Access Max Mix temps and Forecast for rain snow and wind

This commit is contained in:
Lorenz Hohermuth 2025-03-23 19:46:55 +01:00
parent 2c7dc727d3
commit 49b9553118
6 changed files with 206 additions and 33 deletions

View File

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <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> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -66,4 +66,9 @@ dependencies {
implementation("androidx.datastore:datastore:1.0.0") implementation("androidx.datastore:datastore:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") // For JSON serialization 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")
} }

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -2,11 +2,12 @@ package com.module.breeze
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.CalendarContract
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues 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.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AcUnit 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.Map
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.WaterDrop 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.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.module.breeze.ui.theme.BreezeTheme 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 fontSizeCurrentTemp = 48.sp
val fontSizeUpper = 16.sp val fontSizeUpper = 16.sp
val fontSizeTitle = 24.sp val fontSizeTitle = 22.sp
val breezeFontWeight = FontWeight.Bold val breezeFontWeight = FontWeight.Bold
val iconStyle = Icons.Outlined; val iconStyle = Icons.Outlined;
val textColor = Color(0, 0, 0) val textColor = Color(0, 0, 0)
val numberFormat = DecimalFormat("00")
val apiKey = "8a6090c4308455152cd8c677b802883b"
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { 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 @Composable
fun WeatherInfo(modifier: Modifier = Modifier) { fun WeatherInfo(modifier: Modifier = Modifier) {
val ctx = LocalContext.current val ctx = LocalContext.current
var forecast by remember { mutableStateOf(ForecastSummary(0.0, 0.0, false, false, false)) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var displayRain = false; forecast = fetchWeather(ctx)
var displaySnow = false; }
var displayWind = false;
Navigation( Navigation(
iconStyle.Settings, iconStyle.Settings,
@ -121,18 +146,18 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
Icon( Icon(
imageVector = iconStyle.WaterDrop, imageVector = iconStyle.WaterDrop,
contentDescription = "Rain Icon", contentDescription = "Rain Icon",
modifier = Modifier.alpha(if (displayRain) 1F else 0.2F) modifier = Modifier.alpha(if (forecast.hasRain) 1F else 0.2F)
) )
Icon( Icon(
imageVector = iconStyle.AcUnit, imageVector = iconStyle.AcUnit,
contentDescription = "Snow Icon", contentDescription = "Snow Icon",
modifier = Modifier.alpha(if (displaySnow) 1F else 0.2F) modifier = Modifier.alpha(if (forecast.hasSnow) 1F else 0.2F)
) )
Icon( Icon(
imageVector = iconStyle.Air, imageVector = iconStyle.Air,
contentDescription = "Wind Icon", contentDescription = "Wind Icon",
modifier = Modifier.alpha(if (displayWind) 1F else 0.2F) modifier = Modifier.alpha(if (forecast.hasStrongWinds) 1F else 0.2F)
) )
} }
Row { Row {
@ -141,7 +166,7 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
contentDescription = "Arrow down Icon" contentDescription = "Arrow down Icon"
) )
Text( Text(
text = "18°", text = numberFormat.format(forecast.minTemp.roundToInt()),
fontSize = fontSizeUpper, fontSize = fontSizeUpper,
fontWeight = breezeFontWeight fontWeight = breezeFontWeight
) )
@ -150,7 +175,7 @@ fun WeatherInfo(modifier: Modifier = Modifier) {
contentDescription = "Arrow up Icon" contentDescription = "Arrow up Icon"
) )
Text( Text(
text = "23°", text = numberFormat.format(forecast.maxTemp.roundToInt()),
fontSize = fontSizeUpper, fontSize = fontSizeUpper,
fontWeight = breezeFontWeight fontWeight = breezeFontWeight
) )

View File

@ -34,7 +34,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
@ -60,20 +59,20 @@ var plzArrayStatus: List<String> = listOf(
val SHARED_PLZ_KEY = stringPreferencesKey("shared-plz") val SHARED_PLZ_KEY = stringPreferencesKey("shared-plz")
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") 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 -> context.dataStore.edit { preferences ->
preferences[SHARED_PLZ_KEY] = Json.encodeToString(array) 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() val preferences = context.dataStore.data.first()
return preferences[SHARED_PLZ_KEY]?.let { Json.decodeFromString(it) } ?: emptyList() return preferences[SHARED_PLZ_KEY]?.let { Json.decodeFromString(it) } ?: emptyList()
} }
// ### End ChatGPT // ### End ChatGPT
suspend fun getPLZ(index: Int, ctx: Context): String { suspend fun getPLZ(index: Int, ctx: Context): String {
var arr = getArray(ctx) var arr = getLocations(ctx)
if (arr.isEmpty() || arr.size != 7) { if (arr.isEmpty() || arr.size != 7) {
arr = listOf("8005", "8005", "8005", "8400", "8400", "8500", "8500") 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) { suspend fun setPLZ(index: Int, value: String, ctx: Context) {
var arr = plzArrayStatus.toMutableList() var arr = plzArrayStatus.toMutableList()
arr[index] = value arr[index] = value
saveArray(ctx, arr) setLocations(ctx, arr)
} }
@ -139,7 +138,7 @@ fun Settings(modifier: Modifier = Modifier) {
Text( Text(
text = "Configured Locations", text = "Configured Locations",
fontWeight = breezeFontWeight, fontWeight = breezeFontWeight,
fontSize = 22.sp fontSize = fontSizeTitle
) )
} }
ConfiguredLocationDay("Monday", 0, ctx) ConfiguredLocationDay("Monday", 0, ctx)

View File

@ -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
)