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

View File

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

View File

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

View File

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

View File

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

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
)