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
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) {}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}

View File

@ -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<Location?>(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<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(
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),
)
}
}
}

View File

@ -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<String> = 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<Preferences> by preferencesDataStore(name = "settings")
suspend fun setLocations(context: Context, array: List<String>) {
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<String> {
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),
)
}
}
}

View File

@ -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<Weather>,
val dt: Long
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
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<Forecast>
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?
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
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
val minTemp: Double,
val maxTemp: Double,
val hasRain: Boolean,
val hasSnow: Boolean,
val hasStrongWinds: Boolean,
)

View File

@ -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
)
darkTheme -> LightColorScheme
else -> LightColorScheme
}
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
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
)
*/
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
)
*/
)

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