linting changes

This commit is contained in:
Lorenz Hohermuth 2025-03-25 11:40:51 +01:00
parent 2f9109d0ae
commit d88bb72378
9 changed files with 451 additions and 512 deletions

View File

@ -1,24 +0,0 @@
package com.module.breeze
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.module.breeze", appContext.packageName)
}
}

View File

@ -11,35 +11,36 @@ import androidx.core.content.ContextCompat
// AI // AI
class GpsRepository(private val context: Context) : LocationListener { class GpsRepository(private val context: Context) : LocationListener {
private var locationManager: LocationManager? = null private var locationManager: LocationManager? = null
private var onLocationChanged: ((Location) -> Unit)? = null private var onLocationChanged: ((Location) -> Unit)? = null
fun startListening(onLocationChanged: (Location) -> Unit) { fun startListening(onLocationChanged: (Location) -> Unit) {
this.onLocationChanged = onLocationChanged this.onLocationChanged = onLocationChanged
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) if (ContextCompat.checkSelfPermission(
== context,
PackageManager.PERMISSION_GRANTED Manifest.permission.ACCESS_FINE_LOCATION,
) { ) == PackageManager.PERMISSION_GRANTED
locationManager?.requestLocationUpdates( ) {
LocationManager.GPS_PROVIDER, locationManager?.requestLocationUpdates(
1000L, LocationManager.GPS_PROVIDER,
1f, 1000L,
this 1f,
) this,
} )
} }
}
fun stopListening() { fun stopListening() {
locationManager?.removeUpdates(this) locationManager?.removeUpdates(this)
} }
override fun onLocationChanged(location: Location) { override fun onLocationChanged(location: Location) {
onLocationChanged?.invoke(location) onLocationChanged?.invoke(location)
} }
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {} override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {} override fun onProviderDisabled(provider: String) {}
} }

View File

@ -1,12 +1,17 @@
package com.module.breeze package com.module.breeze
import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi 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
@ -31,6 +36,7 @@ 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.DisposableEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -45,247 +51,231 @@ import androidx.compose.ui.text.font.FontWeight
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.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.module.breeze.ui.theme.BreezeTheme import com.module.breeze.ui.theme.BreezeTheme
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.text.DecimalFormat import java.text.DecimalFormat
import java.time.LocalDate import java.time.LocalDate
import kotlin.math.roundToInt import kotlin.math.roundToInt
import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
val fontSizeCurrentTemp = 48.sp val fontSizeCurrentTemp = 48.sp
val fontSizeUpper = 16.sp val fontSizeUpper = 16.sp
val fontSizeTitle = 22.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 numberFormat = DecimalFormat("00")
val apiKey = "8a6090c4308455152cd8c677b802883b" val apiKey = "8a6090c4308455152cd8c677b802883b"
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
BreezeTheme { BreezeTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
WeatherInfo( WeatherInfo(
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding),
) )
}
}
} }
}
} }
}
companion object { companion object {
fun openSettings(ctx: Context) { fun openSettings(ctx: Context) {
var intent = Intent(ctx, SettingsActivity::class.java) var intent = Intent(ctx, SettingsActivity::class.java)
ctx.startActivity(intent) ctx.startActivity(intent)
}
} }
}
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun getTodaysLocation(ctx: Context): String { fun getTodaysLocation(ctx: Context): String {
var dayOfTheWeek = LocalDate.now().dayOfWeek.value - 1 var dayOfTheWeek = LocalDate.now().dayOfWeek.value - 1
var plz = "Loading ..." var plz = "Loading ..."
runBlocking { runBlocking {
plz = getLocations(ctx)[dayOfTheWeek] plz = getLocations(ctx)[dayOfTheWeek]
} }
return plz return plz
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun fetchWeather(ctx: Context): ForecastSummary { fun fetchWeather(ctx: Context): ForecastSummary {
var todayPlz = getTodaysLocation(ctx) var todayPlz = getTodaysLocation(ctx)
var data: ForecastSummary var data: ForecastSummary
runBlocking { runBlocking {
val forecastResponse = RetrofitClient.instance.getWeatherForecast("${todayPlz},ch", apiKey) val forecastResponse = RetrofitClient.instance.getWeatherForecast("$todayPlz,ch", apiKey)
val todaysForecast = getTodaysForecast(forecastResponse) val todaysForecast = getTodaysForecast(forecastResponse)
data = todaysForecast data = todaysForecast
println("Today's Forecast for ${todayPlz}:") }
println("Min Temperature: ${todaysForecast.minTemp}°C") return data
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
} }
fun fetchCurrentTemp(ctx: Context, lat: Double, lon: Double): WeatherResponse { fun fetchCurrentTemp(ctx: Context, lat: Double, lon: Double): WeatherResponse {
var data: WeatherResponse var data: WeatherResponse
runBlocking { runBlocking {
data = RetrofitClient.instance.getCurrentWeather(lat, lon, apiKey) data = RetrofitClient.instance.getCurrentWeather(lat, lon, apiKey)
} }
return data 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)) } var forecast by remember { mutableStateOf(ForecastSummary(0.0, 0.0, false, false, false)) }
var currentTemp by remember { var currentTemp by remember {
mutableStateOf( mutableStateOf(
WeatherResponse(Main(0.0, 0.0, 0.0, 0.0, 0, 0), emptyList(), 0L) WeatherResponse(Main(0.0, 0.0, 0.0, 0.0, 0, 0), emptyList(), 0L),
)
}
val locationHelper = remember { GpsRepository(ctx) }
var location by remember { mutableStateOf<Location?>(null) }
val locationPermission = ContextCompat.checkSelfPermission(
ctx, Manifest.permission.ACCESS_FINE_LOCATION
) )
var hasLocationPermission by remember { }
mutableStateOf(locationPermission == PackageManager.PERMISSION_GRANTED) val locationHelper = remember { GpsRepository(ctx) }
var location by remember { mutableStateOf<Location?>(null) }
val locationPermission = ContextCompat.checkSelfPermission(
ctx,
Manifest.permission.ACCESS_FINE_LOCATION,
)
var hasLocationPermission by remember {
mutableStateOf(locationPermission == PackageManager.PERMISSION_GRANTED)
}
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission(),
) { isGranted -> hasLocationPermission = isGranted }
DisposableEffect(key1 = locationHelper, key2 = hasLocationPermission) {
if (hasLocationPermission) {
locationHelper.startListening { newLocation ->
location = newLocation
}
} }
val permissionLauncher = rememberLauncherForActivityResult( onDispose {
ActivityResultContracts.RequestPermission() locationHelper.stopListening()
) { isGranted -> hasLocationPermission = isGranted }
DisposableEffect(key1 = locationHelper, key2 = hasLocationPermission) {
if (hasLocationPermission) {
locationHelper.startListening { newLocation ->
location = newLocation
}
}
onDispose {
locationHelper.stopListening()
}
} }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
forecast = fetchWeather(ctx) forecast = fetchWeather(ctx)
}
Navigation(
iconStyle.Settings,
"Settings Icon",
onClick = { MainActivity.openSettings(ctx) },
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(verticalAlignment = Alignment.Bottom) {
Icon(
imageVector = iconStyle.Map,
contentDescription = "Location Icon",
modifier = Modifier
.padding(end = 16.dp)
.size(32.dp),
)
Icon(
imageVector = iconStyle.WaterDrop,
contentDescription = "Rain Icon",
modifier = Modifier.alpha(if (forecast.hasRain) 1F else 0.2F),
)
Icon(
imageVector = iconStyle.AcUnit,
contentDescription = "Snow Icon",
modifier = Modifier.alpha(if (forecast.hasSnow) 1F else 0.2F),
)
Icon(
imageVector = iconStyle.Air,
contentDescription = "Wind Icon",
modifier = Modifier.alpha(if (forecast.hasStrongWinds) 1F else 0.2F),
)
} }
Row {
Icon(
Navigation( imageVector = iconStyle.ArrowDownward,
iconStyle.Settings, "Settings Icon", onClick = { MainActivity.openSettings(ctx) }) contentDescription = "Arrow down Icon",
)
Column( Text(
modifier = Modifier.fillMaxSize(), text = numberFormat.format(forecast.minTemp.roundToInt()) + "°",
verticalArrangement = Arrangement.Center, fontSize = fontSizeUpper,
horizontalAlignment = Alignment.CenterHorizontally, fontWeight = breezeFontWeight,
) { )
Row(verticalAlignment = Alignment.Bottom) { Icon(
Icon( imageVector = iconStyle.ArrowUpward,
imageVector = iconStyle.Map, contentDescription = "Arrow up Icon",
contentDescription = "Location Icon", )
modifier = Modifier Text(
.padding(end = 16.dp) text = numberFormat.format(forecast.maxTemp.roundToInt()) + "°",
.size(32.dp) fontSize = fontSizeUpper,
) fontWeight = breezeFontWeight,
Icon( )
imageVector = iconStyle.WaterDrop,
contentDescription = "Rain Icon",
modifier = Modifier.alpha(if (forecast.hasRain) 1F else 0.2F)
)
Icon(
imageVector = iconStyle.AcUnit,
contentDescription = "Snow Icon",
modifier = Modifier.alpha(if (forecast.hasSnow) 1F else 0.2F)
)
Icon(
imageVector = iconStyle.Air,
contentDescription = "Wind Icon",
modifier = Modifier.alpha(if (forecast.hasStrongWinds) 1F else 0.2F)
)
}
Row {
Icon(
imageVector = iconStyle.ArrowDownward, contentDescription = "Arrow down Icon"
)
Text(
text = numberFormat.format(forecast.minTemp.roundToInt()) + "°",
fontSize = fontSizeUpper,
fontWeight = breezeFontWeight
)
Icon(
imageVector = iconStyle.ArrowUpward, contentDescription = "Arrow up Icon"
)
Text(
text = numberFormat.format(forecast.maxTemp.roundToInt()) + "°",
fontSize = fontSizeUpper,
fontWeight = breezeFontWeight
)
}
if (hasLocationPermission) {
location?.let {
currentTemp = fetchCurrentTemp(ctx, it.latitude, it.latitude)
}
Text(
text = numberFormat.format(currentTemp.main.temp.roundToInt()) + "°",
fontSize = fontSizeCurrentTemp,
fontWeight = breezeFontWeight,
)
} else {
Button(
onClick = {
permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(0, 0, 0, 20), contentColor = textColor
),
) {
Text(
text = "Grant Permission",
fontWeight = breezeFontWeight,
)
}
}
} }
if (hasLocationPermission) {
location?.let {
currentTemp = fetchCurrentTemp(ctx, it.latitude, it.latitude)
}
Text(
text = numberFormat.format(currentTemp.main.temp.roundToInt()) + "°",
fontSize = fontSizeCurrentTemp,
fontWeight = breezeFontWeight,
)
} else {
Button(
onClick = {
permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(0, 0, 0, 20),
contentColor = textColor,
),
) {
Text(
text = "Grant Permission",
fontWeight = breezeFontWeight,
)
}
}
}
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun GreetingPreview() { fun GreetingPreview() {
BreezeTheme { BreezeTheme {
WeatherInfo() WeatherInfo()
} }
} }
@Composable @Composable
fun Navigation(icon: ImageVector, iconDescription: String, onClick: () -> Unit = {}) { fun Navigation(icon: ImageVector, iconDescription: String, onClick: () -> Unit = {}) {
Row( Row(
horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth() horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth(),
) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
containerColor = Color(255, 0, 0, 0),
contentColor = textColor,
),
shape = CircleShape,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.padding(0.dp, 40.dp, 28.dp, 0.dp),
) { ) {
Button( Icon(
onClick = onClick, imageVector = icon,
colors = ButtonDefaults.buttonColors( contentDescription = iconDescription,
containerColor = Color(255, 0, 0, 0), contentColor = textColor modifier = Modifier.size(40.dp),
), )
shape = CircleShape,
contentPadding = PaddingValues(0.dp),
modifier = Modifier.padding(0.dp, 40.dp, 28.dp, 0.dp)
) {
Icon(
imageVector = icon,
contentDescription = iconDescription,
modifier = Modifier.size(40.dp)
)
}
} }
}
} }

View File

@ -1,6 +1,5 @@
package com.module.breeze package com.module.breeze
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -45,165 +44,161 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
var plzArrayStatus: List<String> = listOf( var plzArrayStatus: List<String> = listOf(
"Loading...", "Loading...",
"Loading...", "Loading...",
"Loading...", "Loading...",
"Loading...", "Loading...",
"Loading...", "Loading...",
"Loading...", "Loading...",
"Loading...", "Loading...",
) )
// ### Start ChatGPT + Fixing // ### Start ChatGPT + Fixing
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 setLocations(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 getLocations(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 = getLocations(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")
} }
plzArrayStatus = arr plzArrayStatus = arr
return arr[index] return arr[index]
} }
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
setLocations(ctx, arr) setLocations(ctx, arr)
} }
class SettingsActivity : ComponentActivity() { class SettingsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
BreezeTheme { BreezeTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Settings( Settings(
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding),
) )
}
}
} }
}
} }
}
companion object { companion object {
fun openHome(ctx: Context) { fun openHome(ctx: Context) {
var intent = Intent(ctx, MainActivity::class.java) var intent = Intent(ctx, MainActivity::class.java)
ctx.startActivity(intent) ctx.startActivity(intent)
}
} }
}
} }
@Composable @Composable
fun Settings(modifier: Modifier = Modifier) { fun Settings(modifier: Modifier = Modifier) {
val ctx = LocalContext.current val ctx = LocalContext.current
Navigation(iconStyle.Home, "Home Icon", onClick = { Navigation(
SettingsActivity.openHome(ctx) iconStyle.Home,
}) "Home Icon",
Column( onClick = { SettingsActivity.openHome(ctx) },
modifier = Modifier.fillMaxSize(), )
verticalArrangement = Arrangement.Center, Column(
horizontalAlignment = Alignment.CenterHorizontally modifier = Modifier.fillMaxSize(),
) verticalArrangement = Arrangement.Center,
{ horizontalAlignment = Alignment.CenterHorizontally,
Row( ) {
verticalAlignment = Alignment.CenterVertically, Row(
modifier = Modifier.padding(16.dp) verticalAlignment = Alignment.CenterVertically,
) modifier = Modifier.padding(16.dp),
{ ) {
Icon( Icon(
imageVector = iconStyle.Map, imageVector = iconStyle.Map,
contentDescription = "Configured Locations Icon", contentDescription = "Configured Locations Icon",
modifier = Modifier.size(40.dp) modifier = Modifier.size(40.dp),
) )
Text( Text(
text = "Configured Locations", text = "Configured Locations",
fontWeight = breezeFontWeight, fontWeight = breezeFontWeight,
fontSize = fontSizeTitle fontSize = fontSizeTitle,
) )
}
ConfiguredLocationDay("Monday", 0, ctx)
ConfiguredLocationDay("Tuesday", 1, ctx)
ConfiguredLocationDay("Wednesday", 2, ctx)
ConfiguredLocationDay("Thursday", 3, ctx)
ConfiguredLocationDay("Friday", 4, ctx)
ConfiguredLocationDay("Saturday", 5, ctx)
ConfiguredLocationDay("Sunday", 6, ctx)
} }
ConfiguredLocationDay("Monday", 0, ctx)
ConfiguredLocationDay("Tuesday", 1, ctx)
ConfiguredLocationDay("Wednesday", 2, ctx)
ConfiguredLocationDay("Thursday", 3, ctx)
ConfiguredLocationDay("Friday", 4, ctx)
ConfiguredLocationDay("Saturday", 5, ctx)
ConfiguredLocationDay("Sunday", 6, ctx)
}
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun GreetingPreview2() { fun GreetingPreview2() {
BreezeTheme { BreezeTheme {
Settings() Settings()
} }
} }
@Composable @Composable
fun ConfiguredLocationDay(dayName: String = "day", index: Int, ctx: Context) { fun ConfiguredLocationDay(dayName: String = "day", index: Int, ctx: Context) {
var plz by remember { mutableStateOf("") } var plz by remember { mutableStateOf("") }
var isLoadingGet by remember { mutableStateOf(false) } var isLoadingGet by remember { mutableStateOf(false) }
var isLoadingSet by remember { mutableStateOf(false) } var isLoadingSet by remember { mutableStateOf(false) }
LaunchedEffect(index) { LaunchedEffect(index) {
isLoadingGet = true isLoadingGet = true
plz = getPLZ(index, ctx) plz = getPLZ(index, ctx)
isLoadingGet = false isLoadingGet = false
} }
LaunchedEffect(plz) { LaunchedEffect(plz) {
if (plz.length == 4) { if (plz.length == 4) {
isLoadingSet = true isLoadingSet = true
setPLZ(index, plz, ctx) setPLZ(index, plz, ctx)
isLoadingSet = false isLoadingSet = false
}
} }
}
if (isLoadingSet || isLoadingGet) { if (isLoadingSet || isLoadingGet) {
CircularProgressIndicator() CircularProgressIndicator()
} else { } else {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(0.dp, 4.dp) modifier = Modifier.padding(0.dp, 4.dp),
) { ) {
Text( Text(
text = dayName, text = dayName,
fontWeight = breezeFontWeight, fontWeight = breezeFontWeight,
modifier = Modifier.width(100.dp) modifier = Modifier.width(100.dp),
) )
TextField( TextField(
value = plz, value = plz,
onValueChange = { newPlz -> onValueChange = { newPlz ->
var cutNewPlz = newPlz var cutNewPlz = newPlz
if (newPlz.length > 4) { if (newPlz.length > 4) {
cutNewPlz = newPlz.substring(0, 4) cutNewPlz = newPlz.substring(0, 4)
} }
plz = cutNewPlz plz = cutNewPlz
}, },
label = { Text("PLZ") }, label = { Text("PLZ") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.width(110.dp) modifier = Modifier.width(110.dp),
) )
}
} }
} }
}

View File

@ -1,134 +1,129 @@
package com.module.breeze package com.module.breeze
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.util.Date import java.util.Date
// Whole File DeepSeek // Whole File DeepSeek
// Data classes for API responses // Data classes for API responses
data class WeatherResponse( data class WeatherResponse(
val main: Main, val main: Main,
val weather: List<Weather>, val weather: List<Weather>,
val dt: Long val dt: Long,
) )
data class Main( data class Main(
val temp: Double, val temp: Double,
val feels_like: Double, val feels_like: Double,
val temp_min: Double, val temp_min: Double,
val temp_max: Double, val temp_max: Double,
val pressure: Int, val pressure: Int,
val humidity: Int val humidity: Int,
) )
data class Weather( data class Weather(
val id: Int, val id: Int,
val main: String, val main: String,
val description: String, val description: String,
val icon: String val icon: String,
) )
data class ForecastResponse( data class ForecastResponse(
val list: List<Forecast> val list: List<Forecast>,
) )
data class Forecast( data class Forecast(
val dt: Long, val dt: Long,
val main: Main, val main: Main,
val weather: List<Weather>, val weather: List<Weather>,
val wind: Wind, val wind: Wind,
val rain: Rain?, val rain: Rain?,
val snow: Snow? val snow: Snow?,
) )
data class Wind( data class Wind(
val speed: Double, val speed: Double,
val deg: Int val deg: Int,
) )
data class Rain( data class Rain(
val `3h`: Double? val `3h`: Double?,
) )
data class Snow( data class Snow(
val `3h`: Double? val `3h`: Double?,
) )
// Retrofit API interface // Retrofit API interface
interface WeatherApiService { interface WeatherApiService {
@GET("weather") @GET("weather")
suspend fun getCurrentWeather( suspend fun getCurrentWeather(
@Query("lat") lat: Double, @Query("lat") lat: Double,
@Query("lon") lon: Double, @Query("lon") lon: Double,
@Query("appid") apiKey: String, @Query("appid") apiKey: String,
@Query("units") units: String = "metric" @Query("units") units: String = "metric",
): WeatherResponse ): WeatherResponse
@GET("forecast") @GET("forecast")
suspend fun getWeatherForecast( suspend fun getWeatherForecast(
@Query("zip") zip: String, @Query("zip") zip: String,
@Query("appid") apiKey: String, @Query("appid") apiKey: String,
@Query("units") units: String = "metric" @Query("units") units: String = "metric",
): ForecastResponse ): ForecastResponse
} }
// Retrofit client setup // Retrofit client setup
object RetrofitClient { object RetrofitClient {
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/" private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
private val logging = HttpLoggingInterceptor().apply { private val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY level = HttpLoggingInterceptor.Level.BODY
} }
private val httpClient = OkHttpClient.Builder() private val httpClient = OkHttpClient.Builder().addInterceptor(logging).build()
.addInterceptor(logging)
.build()
val instance: WeatherApiService by lazy { val instance: WeatherApiService by lazy {
Retrofit.Builder() Retrofit.Builder().baseUrl(BASE_URL).client(httpClient)
.baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()).build()
.client(httpClient) .create(WeatherApiService::class.java)
.addConverterFactory(GsonConverterFactory.create()) }
.build()
.create(WeatherApiService::class.java)
}
} }
// Helper function to filter today's forecast // Helper function to filter today's forecast
fun getTodaysForecast(forecastResponse: ForecastResponse): ForecastSummary { fun getTodaysForecast(forecastResponse: ForecastResponse): ForecastSummary {
val today = Date().time / 1000 // Current timestamp in seconds val today = Date().time / 1000 // Current timestamp in seconds
val tomorrow = today + 86400 // 24 hours later val tomorrow = today + 86400 // 24 hours later
// Filter forecasts for today // Filter forecasts for today
val todaysForecasts = forecastResponse.list.filter { it.dt in today..tomorrow } val todaysForecasts = forecastResponse.list.filter { it.dt in today..tomorrow }
// Extract min and max temperatures // Extract min and max temperatures
val minTemp = todaysForecasts.minOfOrNull { it.main.temp_min } ?: 0.0 val minTemp = todaysForecasts.minOfOrNull { it.main.temp_min } ?: 0.0
val maxTemp = todaysForecasts.maxOfOrNull { it.main.temp_max } ?: 0.0 val maxTemp = todaysForecasts.maxOfOrNull { it.main.temp_max } ?: 0.0
// Check for rain, snow, or strong winds // Check for rain, snow, or strong winds
val hasRain = todaysForecasts.any { it.rain != null } val hasRain = todaysForecasts.any { it.rain != null }
val hasSnow = todaysForecasts.any { it.snow != null } val hasSnow = todaysForecasts.any { it.snow != null }
val hasStrongWinds = todaysForecasts.any { it.wind.speed > 10.0 } // Wind speed > 10 m/s val hasStrongWinds = todaysForecasts.any { it.wind.speed > 10.0 } // Wind speed > 10 m/s
return ForecastSummary( return ForecastSummary(
minTemp = minTemp, minTemp = minTemp,
maxTemp = maxTemp, maxTemp = maxTemp,
hasRain = hasRain, hasRain = hasRain,
hasSnow = hasSnow, hasSnow = hasSnow,
hasStrongWinds = hasStrongWinds hasStrongWinds = hasStrongWinds,
) )
} }
// Data class for today's forecast summary // Data class for today's forecast summary
data class ForecastSummary( data class ForecastSummary(
val minTemp: Double, val minTemp: Double,
val maxTemp: Double, val maxTemp: Double,
val hasRain: Boolean, val hasRain: Boolean,
val hasSnow: Boolean, val hasSnow: Boolean,
val hasStrongWinds: Boolean val hasStrongWinds: Boolean,
) )

View File

@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4) val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71) val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260) val Pink40 = Color(0xFF7D5260)

View File

@ -1,6 +1,5 @@
package com.module.breeze.ui.theme package com.module.breeze.ui.theme
import android.app.Activity
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -12,47 +11,47 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = Purple80, primary = Purple80,
secondary = PurpleGrey80, secondary = PurpleGrey80,
tertiary = Pink80 tertiary = Pink80,
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(
primary = Purple40, primary = Purple40,
secondary = PurpleGrey40, secondary = PurpleGrey40,
tertiary = Pink40 tertiary = Pink40,
/* Other default colors to override /* Other default colors to override
background = Color(0xFFFFFBFE), background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE), surface = Color(0xFFFFFBFE),
onPrimary = Color.White, onPrimary = Color.White,
onSecondary = Color.White, onSecondary = Color.White,
onTertiary = Color.White, onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F), onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F),
*/ */
) )
@Composable @Composable
fun BreezeTheme( fun BreezeTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+ // Dynamic color is available on Android 12+
dynamicColor: Boolean = true, dynamicColor: Boolean = true,
content: @Composable () -> Unit content: @Composable () -> Unit,
) { ) {
val colorScheme = when { val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> LightColorScheme
else -> LightColorScheme
} }
MaterialTheme( darkTheme -> LightColorScheme
colorScheme = colorScheme, else -> LightColorScheme
typography = Typography, }
content = content
) MaterialTheme(
} colorScheme = colorScheme,
typography = Typography,
content = content,
)
}

View File

@ -8,27 +8,27 @@ import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with // Set of Material typography styles to start with
val Typography = Typography( val Typography = Typography(
bodyLarge = TextStyle( bodyLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 16.sp, fontSize = 16.sp,
lineHeight = 24.sp, lineHeight = 24.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp,
) ),
/* Other default text styles to override /* Other default text styles to override
titleLarge = TextStyle( titleLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 22.sp, fontSize = 22.sp,
lineHeight = 28.sp, lineHeight = 28.sp,
letterSpacing = 0.sp letterSpacing = 0.sp
), ),
labelSmall = TextStyle( labelSmall = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 11.sp, fontSize = 11.sp,
lineHeight = 16.sp, lineHeight = 16.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
) )
*/ */
) )

View File

@ -1,17 +0,0 @@
package com.module.breeze
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}