Access Max Mix temps and Forecast for rain snow and wind
This commit is contained in:
parent
2c7dc727d3
commit
49b9553118
|
@ -4,6 +4,14 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-03-23T17:25:15.945565980Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lorenz/.android/avd/Medium_Phone_API_35.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
|
|
@ -66,4 +66,9 @@ dependencies {
|
|||
implementation("androidx.datastore:datastore:1.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") // For JSON serialization
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
|
||||
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
|
|
@ -2,11 +2,12 @@ package com.module.breeze
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.CalendarContract
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
@ -15,7 +16,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AcUnit
|
||||
|
@ -25,31 +25,20 @@ import androidx.compose.material.icons.outlined.ArrowUpward
|
|||
import androidx.compose.material.icons.outlined.Map
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.WaterDrop
|
||||
import androidx.compose.material.icons.rounded.AcUnit
|
||||
import androidx.compose.material.icons.rounded.Air
|
||||
import androidx.compose.material.icons.rounded.ArrowDownward
|
||||
import androidx.compose.material.icons.rounded.ArrowDropDown
|
||||
import androidx.compose.material.icons.rounded.ArrowUpward
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.rounded.LocationOn
|
||||
import androidx.compose.material.icons.rounded.Map
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material.icons.rounded.WaterDrop
|
||||
import androidx.compose.material.icons.sharp.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.twotone.AccountCircle
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
@ -57,14 +46,20 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.module.breeze.ui.theme.BreezeTheme
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.text.DecimalFormat
|
||||
import java.time.LocalDate
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
val fontSizeCurrentTemp = 48.sp
|
||||
val fontSizeUpper = 16.sp
|
||||
val fontSizeTitle = 24.sp
|
||||
val fontSizeTitle = 22.sp
|
||||
val breezeFontWeight = FontWeight.Bold
|
||||
val iconStyle = Icons.Outlined;
|
||||
val textColor = Color(0, 0, 0)
|
||||
val numberFormat = DecimalFormat("00")
|
||||
val apiKey = "8a6090c4308455152cd8c677b802883b"
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -89,14 +84,44 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getTodaysLocation(ctx: Context): String {
|
||||
var dayOfTheWeek = LocalDate.now().dayOfWeek.value - 1
|
||||
var plz = "Loading ..."
|
||||
runBlocking {
|
||||
plz = getLocations(ctx)[dayOfTheWeek]
|
||||
}
|
||||
return plz
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun fetchWeather(ctx: Context): ForecastSummary {
|
||||
var todayPlz = getTodaysLocation(ctx)
|
||||
|
||||
var data: ForecastSummary
|
||||
runBlocking {
|
||||
val forecastResponse = RetrofitClient.instance.getWeatherForecast("${todayPlz},ch", apiKey)
|
||||
val todaysForecast = getTodaysForecast(forecastResponse)
|
||||
|
||||
data = todaysForecast
|
||||
println("Today's Forecast for ${todayPlz}:")
|
||||
println("Min Temperature: ${todaysForecast.minTemp}°C")
|
||||
println("Max Temperature: ${todaysForecast.maxTemp}°C")
|
||||
println("Rain: ${if (todaysForecast.hasRain) "Yes" else "No"}")
|
||||
println("Snow: ${if (todaysForecast.hasSnow) "Yes" else "No"}")
|
||||
println("Strong Winds: ${if (todaysForecast.hasStrongWinds) "Yes" else "No"}")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WeatherInfo(modifier: Modifier = Modifier) {
|
||||
val ctx = LocalContext.current
|
||||
var forecast by remember { mutableStateOf(ForecastSummary(0.0, 0.0, false, false, false)) }
|
||||
|
||||
|
||||
var displayRain = false;
|
||||
var displaySnow = false;
|
||||
var displayWind = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
forecast = fetchWeather(ctx)
|
||||
}
|
||||
|
||||
Navigation(
|
||||
iconStyle.Settings,
|
||||
|
@ -121,18 +146,18 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
|
|||
Icon(
|
||||
imageVector = iconStyle.WaterDrop,
|
||||
contentDescription = "Rain Icon",
|
||||
modifier = Modifier.alpha(if (displayRain) 1F else 0.2F)
|
||||
modifier = Modifier.alpha(if (forecast.hasRain) 1F else 0.2F)
|
||||
)
|
||||
Icon(
|
||||
imageVector = iconStyle.AcUnit,
|
||||
contentDescription = "Snow Icon",
|
||||
modifier = Modifier.alpha(if (displaySnow) 1F else 0.2F)
|
||||
modifier = Modifier.alpha(if (forecast.hasSnow) 1F else 0.2F)
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = iconStyle.Air,
|
||||
contentDescription = "Wind Icon",
|
||||
modifier = Modifier.alpha(if (displayWind) 1F else 0.2F)
|
||||
modifier = Modifier.alpha(if (forecast.hasStrongWinds) 1F else 0.2F)
|
||||
)
|
||||
}
|
||||
Row {
|
||||
|
@ -141,7 +166,7 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
|
|||
contentDescription = "Arrow down Icon"
|
||||
)
|
||||
Text(
|
||||
text = "18°",
|
||||
text = numberFormat.format(forecast.minTemp.roundToInt()),
|
||||
fontSize = fontSizeUpper,
|
||||
fontWeight = breezeFontWeight
|
||||
)
|
||||
|
@ -150,7 +175,7 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
|
|||
contentDescription = "Arrow up Icon"
|
||||
)
|
||||
Text(
|
||||
text = "23°",
|
||||
text = numberFormat.format(forecast.maxTemp.roundToInt()),
|
||||
fontSize = fontSizeUpper,
|
||||
fontWeight = breezeFontWeight
|
||||
)
|
||||
|
|
|
@ -34,7 +34,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
|
@ -60,20 +59,20 @@ var plzArrayStatus: List<String> = listOf(
|
|||
val SHARED_PLZ_KEY = stringPreferencesKey("shared-plz")
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
suspend fun saveArray(context: Context, array: List<String>) {
|
||||
suspend fun setLocations(context: Context, array: List<String>) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[SHARED_PLZ_KEY] = Json.encodeToString(array)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getArray(context: Context): List<String> {
|
||||
suspend fun getLocations(context: Context): List<String> {
|
||||
val preferences = context.dataStore.data.first()
|
||||
return preferences[SHARED_PLZ_KEY]?.let { Json.decodeFromString(it) } ?: emptyList()
|
||||
}
|
||||
// ### End ChatGPT
|
||||
|
||||
suspend fun getPLZ(index: Int, ctx: Context): String {
|
||||
var arr = getArray(ctx)
|
||||
var arr = getLocations(ctx)
|
||||
if (arr.isEmpty() || arr.size != 7) {
|
||||
arr = listOf("8005", "8005", "8005", "8400", "8400", "8500", "8500")
|
||||
}
|
||||
|
@ -84,7 +83,7 @@ suspend fun getPLZ(index: Int, ctx: Context): String {
|
|||
suspend fun setPLZ(index: Int, value: String, ctx: Context) {
|
||||
var arr = plzArrayStatus.toMutableList()
|
||||
arr[index] = value
|
||||
saveArray(ctx, arr)
|
||||
setLocations(ctx, arr)
|
||||
}
|
||||
|
||||
|
||||
|
@ -139,7 +138,7 @@ fun Settings(modifier: Modifier = Modifier) {
|
|||
Text(
|
||||
text = "Configured Locations",
|
||||
fontWeight = breezeFontWeight,
|
||||
fontSize = 22.sp
|
||||
fontSize = fontSizeTitle
|
||||
)
|
||||
}
|
||||
ConfiguredLocationDay("Monday", 0, ctx)
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package com.module.breeze
|
||||
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import java.util.Date
|
||||
|
||||
// Whole File DeepSeek
|
||||
// Data classes for API responses
|
||||
data class WeatherResponse(
|
||||
val main: Main,
|
||||
val weather: List<Weather>,
|
||||
val dt: Long
|
||||
)
|
||||
|
||||
data class Main(
|
||||
val temp: Double,
|
||||
val feels_like: Double,
|
||||
val temp_min: Double,
|
||||
val temp_max: Double,
|
||||
val pressure: Int,
|
||||
val humidity: Int
|
||||
)
|
||||
|
||||
data class Weather(
|
||||
val id: Int,
|
||||
val main: String,
|
||||
val description: String,
|
||||
val icon: String
|
||||
)
|
||||
|
||||
data class ForecastResponse(
|
||||
val list: List<Forecast>
|
||||
)
|
||||
|
||||
data class Forecast(
|
||||
val dt: Long,
|
||||
val main: Main,
|
||||
val weather: List<Weather>,
|
||||
val wind: Wind,
|
||||
val rain: Rain?,
|
||||
val snow: Snow?
|
||||
)
|
||||
|
||||
data class Wind(
|
||||
val speed: Double,
|
||||
val deg: Int
|
||||
)
|
||||
|
||||
data class Rain(
|
||||
val `3h`: Double?
|
||||
)
|
||||
|
||||
data class Snow(
|
||||
val `3h`: Double?
|
||||
)
|
||||
|
||||
// Retrofit API interface
|
||||
interface WeatherApiService {
|
||||
@GET("weather")
|
||||
suspend fun getCurrentWeather(
|
||||
@Query("lat") lat: Double,
|
||||
@Query("lon") lon: Double,
|
||||
@Query("appid") apiKey: String,
|
||||
@Query("units") units: String = "metric"
|
||||
): WeatherResponse
|
||||
|
||||
@GET("forecast")
|
||||
suspend fun getWeatherForecast(
|
||||
@Query("zip") zip: String,
|
||||
@Query("appid") apiKey: String,
|
||||
@Query("units") units: String = "metric"
|
||||
): ForecastResponse
|
||||
}
|
||||
|
||||
// Retrofit client setup
|
||||
object RetrofitClient {
|
||||
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
|
||||
|
||||
private val logging = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
private val httpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(logging)
|
||||
.build()
|
||||
|
||||
val instance: WeatherApiService by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(httpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(WeatherApiService::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to filter today's forecast
|
||||
fun getTodaysForecast(forecastResponse: ForecastResponse): ForecastSummary {
|
||||
val today = Date().time / 1000 // Current timestamp in seconds
|
||||
val tomorrow = today + 86400 // 24 hours later
|
||||
|
||||
// Filter forecasts for today
|
||||
val todaysForecasts = forecastResponse.list.filter { it.dt in today..tomorrow }
|
||||
|
||||
// Extract min and max temperatures
|
||||
val minTemp = todaysForecasts.minOfOrNull { it.main.temp_min } ?: 0.0
|
||||
val maxTemp = todaysForecasts.maxOfOrNull { it.main.temp_max } ?: 0.0
|
||||
|
||||
// Check for rain, snow, or strong winds
|
||||
val hasRain = todaysForecasts.any { it.rain != null }
|
||||
val hasSnow = todaysForecasts.any { it.snow != null }
|
||||
val hasStrongWinds = todaysForecasts.any { it.wind.speed > 10.0 } // Wind speed > 10 m/s
|
||||
|
||||
return ForecastSummary(
|
||||
minTemp = minTemp,
|
||||
maxTemp = maxTemp,
|
||||
hasRain = hasRain,
|
||||
hasSnow = hasSnow,
|
||||
hasStrongWinds = hasStrongWinds
|
||||
)
|
||||
}
|
||||
|
||||
// Data class for today's forecast summary
|
||||
data class ForecastSummary(
|
||||
val minTemp: Double,
|
||||
val maxTemp: Double,
|
||||
val hasRain: Boolean,
|
||||
val hasSnow: Boolean,
|
||||
val hasStrongWinds: Boolean
|
||||
)
|
Loading…
Reference in New Issue