diff --git a/app/src/androidTest/java/com/module/breeze/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/module/breeze/ExampleInstrumentedTest.kt deleted file mode 100644 index d246357..0000000 --- a/app/src/androidTest/java/com/module/breeze/ExampleInstrumentedTest.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/module/breeze/GpsRepository.kt b/app/src/main/java/com/module/breeze/GpsRepository.kt index 5f35f9b..71d9823 100644 --- a/app/src/main/java/com/module/breeze/GpsRepository.kt +++ b/app/src/main/java/com/module/breeze/GpsRepository.kt @@ -11,35 +11,36 @@ import androidx.core.content.ContextCompat // AI class GpsRepository(private val context: Context) : LocationListener { - private var locationManager: LocationManager? = null - private var onLocationChanged: ((Location) -> Unit)? = null + private var locationManager: LocationManager? = null + private var onLocationChanged: ((Location) -> Unit)? = null - fun startListening(onLocationChanged: (Location) -> Unit) { - this.onLocationChanged = onLocationChanged - locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + fun startListening(onLocationChanged: (Location) -> Unit) { + this.onLocationChanged = onLocationChanged + locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager - if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) - == - PackageManager.PERMISSION_GRANTED - ) { - locationManager?.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - 1000L, - 1f, - this - ) - } + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION, + ) == PackageManager.PERMISSION_GRANTED + ) { + locationManager?.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 1000L, + 1f, + this, + ) } + } - fun stopListening() { - locationManager?.removeUpdates(this) - } + fun stopListening() { + locationManager?.removeUpdates(this) + } - override fun onLocationChanged(location: Location) { - onLocationChanged?.invoke(location) - } + override fun onLocationChanged(location: Location) { + onLocationChanged?.invoke(location) + } - override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} - override fun onProviderEnabled(provider: String) {} - override fun onProviderDisabled(provider: String) {} -} \ No newline at end of file + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} + override fun onProviderEnabled(provider: String) {} + override fun onProviderDisabled(provider: String) {} +} diff --git a/app/src/main/java/com/module/breeze/MainActivity.kt b/app/src/main/java/com/module/breeze/MainActivity.kt index 53d2749..702b8b4 100644 --- a/app/src/main/java/com/module/breeze/MainActivity.kt +++ b/app/src/main/java/com/module/breeze/MainActivity.kt @@ -1,12 +1,17 @@ package com.module.breeze +import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.location.Location import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -31,6 +36,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat import com.module.breeze.ui.theme.BreezeTheme import kotlinx.coroutines.runBlocking import java.text.DecimalFormat import java.time.LocalDate 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 fontSizeUpper = 16.sp val fontSizeTitle = 22.sp val breezeFontWeight = FontWeight.Bold -val iconStyle = Icons.Outlined; +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?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - BreezeTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - WeatherInfo( - modifier = Modifier.padding(innerPadding) - ) - } - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + BreezeTheme { + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + WeatherInfo( + modifier = Modifier.padding(innerPadding), + ) } + } } + } - companion object { - fun openSettings(ctx: Context) { - var intent = Intent(ctx, SettingsActivity::class.java) - ctx.startActivity(intent) - } + companion object { + fun openSettings(ctx: Context) { + var intent = Intent(ctx, SettingsActivity::class.java) + ctx.startActivity(intent) } + } } @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 + 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 todayPlz = getTodaysLocation(ctx) - var data: ForecastSummary - runBlocking { - val forecastResponse = RetrofitClient.instance.getWeatherForecast("${todayPlz},ch", apiKey) - val todaysForecast = getTodaysForecast(forecastResponse) + 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 + data = todaysForecast + } + return data } fun fetchCurrentTemp(ctx: Context, lat: Double, lon: Double): WeatherResponse { - var data: WeatherResponse - runBlocking { - data = RetrofitClient.instance.getCurrentWeather(lat, lon, apiKey) - } - return data + var data: WeatherResponse + runBlocking { + data = RetrofitClient.instance.getCurrentWeather(lat, lon, apiKey) + } + 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 currentTemp by remember { - mutableStateOf( - WeatherResponse(Main(0.0, 0.0, 0.0, 0.0, 0, 0), emptyList(), 0L) - ) - } - val locationHelper = remember { GpsRepository(ctx) } - - var location by remember { mutableStateOf(null) } - - val locationPermission = ContextCompat.checkSelfPermission( - ctx, Manifest.permission.ACCESS_FINE_LOCATION + val ctx = LocalContext.current + var forecast by remember { mutableStateOf(ForecastSummary(0.0, 0.0, false, false, false)) } + var currentTemp by remember { + mutableStateOf( + WeatherResponse(Main(0.0, 0.0, 0.0, 0.0, 0, 0), emptyList(), 0L), ) - var hasLocationPermission by remember { - mutableStateOf(locationPermission == PackageManager.PERMISSION_GRANTED) + } + val locationHelper = remember { GpsRepository(ctx) } + + var location by remember { mutableStateOf(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( - ActivityResultContracts.RequestPermission() - ) { isGranted -> hasLocationPermission = isGranted } - - - DisposableEffect(key1 = locationHelper, key2 = hasLocationPermission) { - if (hasLocationPermission) { - locationHelper.startListening { newLocation -> - location = newLocation - } - } - - onDispose { - locationHelper.stopListening() - } + onDispose { + locationHelper.stopListening() } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - forecast = fetchWeather(ctx) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + 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), + ) } - - - 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( - 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, - ) - } - } + 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, + ) + } + } + } } @Preview(showBackground = true) @Composable fun GreetingPreview() { - BreezeTheme { - WeatherInfo() - } + BreezeTheme { + WeatherInfo() + } } - @Composable fun Navigation(icon: ImageVector, iconDescription: String, onClick: () -> Unit = {}) { - Row( - horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth() + Row( + 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( - 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) - ) { - Icon( - imageVector = icon, - contentDescription = iconDescription, - modifier = Modifier.size(40.dp) - ) - } + Icon( + imageVector = icon, + contentDescription = iconDescription, + modifier = Modifier.size(40.dp), + ) } + } } diff --git a/app/src/main/java/com/module/breeze/SettingsActivity.kt b/app/src/main/java/com/module/breeze/SettingsActivity.kt index 18a0daf..908478c 100644 --- a/app/src/main/java/com/module/breeze/SettingsActivity.kt +++ b/app/src/main/java/com/module/breeze/SettingsActivity.kt @@ -1,6 +1,5 @@ package com.module.breeze - import android.content.Context import android.content.Intent import android.os.Bundle @@ -45,165 +44,161 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json var plzArrayStatus: List = listOf( - "Loading...", - "Loading...", - "Loading...", - "Loading...", - "Loading...", - "Loading...", - "Loading...", + "Loading...", + "Loading...", + "Loading...", + "Loading...", + "Loading...", + "Loading...", + "Loading...", ) - // ### Start ChatGPT + Fixing val SHARED_PLZ_KEY = stringPreferencesKey("shared-plz") val Context.dataStore: DataStore by preferencesDataStore(name = "settings") suspend fun setLocations(context: Context, array: List) { - context.dataStore.edit { preferences -> - preferences[SHARED_PLZ_KEY] = Json.encodeToString(array) - } + context.dataStore.edit { preferences -> + preferences[SHARED_PLZ_KEY] = Json.encodeToString(array) + } } suspend fun getLocations(context: Context): List { - val preferences = context.dataStore.data.first() - return preferences[SHARED_PLZ_KEY]?.let { Json.decodeFromString(it) } ?: emptyList() + 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 = getLocations(ctx) - if (arr.isEmpty() || arr.size != 7) { - arr = listOf("8005", "8005", "8005", "8400", "8400", "8500", "8500") - } - plzArrayStatus = arr - return arr[index] + var arr = getLocations(ctx) + if (arr.isEmpty() || arr.size != 7) { + arr = listOf("8005", "8005", "8005", "8400", "8400", "8500", "8500") + } + plzArrayStatus = arr + return arr[index] } suspend fun setPLZ(index: Int, value: String, ctx: Context) { - var arr = plzArrayStatus.toMutableList() - arr[index] = value - setLocations(ctx, arr) + var arr = plzArrayStatus.toMutableList() + arr[index] = value + setLocations(ctx, arr) } - class SettingsActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - BreezeTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Settings( - modifier = Modifier.padding(innerPadding) - ) - } - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + BreezeTheme { + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + Settings( + modifier = Modifier.padding(innerPadding), + ) } + } } + } - companion object { - fun openHome(ctx: Context) { - var intent = Intent(ctx, MainActivity::class.java) - ctx.startActivity(intent) - } - - + companion object { + fun openHome(ctx: Context) { + var intent = Intent(ctx, MainActivity::class.java) + ctx.startActivity(intent) } + } } @Composable fun Settings(modifier: Modifier = Modifier) { - val ctx = LocalContext.current + val ctx = LocalContext.current - Navigation(iconStyle.Home, "Home Icon", onClick = { - SettingsActivity.openHome(ctx) - }) - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) - { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(16.dp) - ) - { - Icon( - imageVector = iconStyle.Map, - contentDescription = "Configured Locations Icon", - modifier = Modifier.size(40.dp) - ) - Text( - text = "Configured Locations", - fontWeight = breezeFontWeight, - 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) + Navigation( + iconStyle.Home, + "Home Icon", + onClick = { SettingsActivity.openHome(ctx) }, + ) + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp), + ) { + Icon( + imageVector = iconStyle.Map, + contentDescription = "Configured Locations Icon", + modifier = Modifier.size(40.dp), + ) + Text( + text = "Configured Locations", + fontWeight = breezeFontWeight, + 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) + } } @Preview(showBackground = true) @Composable fun GreetingPreview2() { - BreezeTheme { - Settings() - } + BreezeTheme { + Settings() + } } @Composable fun ConfiguredLocationDay(dayName: String = "day", index: Int, ctx: Context) { - var plz by remember { mutableStateOf("") } - var isLoadingGet by remember { mutableStateOf(false) } - var isLoadingSet by remember { mutableStateOf(false) } + var plz by remember { mutableStateOf("") } + var isLoadingGet by remember { mutableStateOf(false) } + var isLoadingSet by remember { mutableStateOf(false) } - LaunchedEffect(index) { - isLoadingGet = true - plz = getPLZ(index, ctx) - isLoadingGet = false - } + LaunchedEffect(index) { + isLoadingGet = true + plz = getPLZ(index, ctx) + isLoadingGet = false + } - LaunchedEffect(plz) { - if (plz.length == 4) { - isLoadingSet = true - setPLZ(index, plz, ctx) - isLoadingSet = false - } + LaunchedEffect(plz) { + if (plz.length == 4) { + isLoadingSet = true + setPLZ(index, plz, ctx) + isLoadingSet = false } + } - if (isLoadingSet || isLoadingGet) { - CircularProgressIndicator() - } else { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(0.dp, 4.dp) - ) { - Text( - text = dayName, - fontWeight = breezeFontWeight, - modifier = Modifier.width(100.dp) - ) - TextField( - value = plz, - onValueChange = { newPlz -> - var cutNewPlz = newPlz - if (newPlz.length > 4) { - cutNewPlz = newPlz.substring(0, 4) - } - plz = cutNewPlz - }, - label = { Text("PLZ") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.width(110.dp) - ) - } + if (isLoadingSet || isLoadingGet) { + CircularProgressIndicator() + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(0.dp, 4.dp), + ) { + Text( + text = dayName, + fontWeight = breezeFontWeight, + modifier = Modifier.width(100.dp), + ) + TextField( + value = plz, + onValueChange = { newPlz -> + var cutNewPlz = newPlz + if (newPlz.length > 4) { + cutNewPlz = newPlz.substring(0, 4) + } + plz = cutNewPlz + }, + label = { Text("PLZ") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.width(110.dp), + ) } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/com/module/breeze/WheatherRepository.kt b/app/src/main/java/com/module/breeze/WheatherRepository.kt index 7831128..fdad1c2 100644 --- a/app/src/main/java/com/module/breeze/WheatherRepository.kt +++ b/app/src/main/java/com/module/breeze/WheatherRepository.kt @@ -1,134 +1,129 @@ package com.module.breeze +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor 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, - val dt: Long + val main: Main, + val weather: List, + 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 + 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 + val id: Int, + val main: String, + val description: String, + val icon: String, ) data class ForecastResponse( - val list: List + val list: List, ) data class Forecast( - val dt: Long, - val main: Main, - val weather: List, - val wind: Wind, - val rain: Rain?, - val snow: Snow? + val dt: Long, + val main: Main, + val weather: List, + val wind: Wind, + val rain: Rain?, + val snow: Snow?, ) data class Wind( - val speed: Double, - val deg: Int + val speed: Double, + val deg: Int, ) data class Rain( - val `3h`: Double? + val `3h`: Double?, ) data class Snow( - val `3h`: Double? + 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("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 + @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 const val BASE_URL = "https://api.openweathermap.org/data/2.5/" - private val logging = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - } + private val logging = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } - private val httpClient = OkHttpClient.Builder() - .addInterceptor(logging) - .build() + 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) - } + 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 + 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 } + // 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 + // 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 + // 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 - ) + 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 -) \ No newline at end of file + val minTemp: Double, + val maxTemp: Double, + val hasRain: Boolean, + val hasSnow: Boolean, + val hasStrongWinds: Boolean, +) diff --git a/app/src/main/java/com/module/breeze/ui/theme/Color.kt b/app/src/main/java/com/module/breeze/ui/theme/Color.kt index b811817..86903e2 100644 --- a/app/src/main/java/com/module/breeze/ui/theme/Color.kt +++ b/app/src/main/java/com/module/breeze/ui/theme/Color.kt @@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) diff --git a/app/src/main/java/com/module/breeze/ui/theme/Theme.kt b/app/src/main/java/com/module/breeze/ui/theme/Theme.kt index 2392b94..5c3f03f 100644 --- a/app/src/main/java/com/module/breeze/ui/theme/Theme.kt +++ b/app/src/main/java/com/module/breeze/ui/theme/Theme.kt @@ -1,6 +1,5 @@ package com.module.breeze.ui.theme -import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -12,47 +11,47 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80, ) private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40, - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ ) @Composable fun BreezeTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, - content: @Composable () -> Unit + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit, ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } - - darkTheme -> LightColorScheme - else -> LightColorScheme + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content - ) -} \ No newline at end of file + darkTheme -> LightColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content, + ) +} diff --git a/app/src/main/java/com/module/breeze/ui/theme/Type.kt b/app/src/main/java/com/module/breeze/ui/theme/Type.kt index 3fc8eae..0e76dde 100644 --- a/app/src/main/java/com/module/breeze/ui/theme/Type.kt +++ b/app/src/main/java/com/module/breeze/ui/theme/Type.kt @@ -8,27 +8,27 @@ import androidx.compose.ui.unit.sp // Set of Material typography styles to start with val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) \ No newline at end of file + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/app/src/test/java/com/module/breeze/ExampleUnitTest.kt b/app/src/test/java/com/module/breeze/ExampleUnitTest.kt deleted file mode 100644 index 5ea5d1e..0000000 --- a/app/src/test/java/com/module/breeze/ExampleUnitTest.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file