package com.module.breeze import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AcUnit import androidx.compose.material.icons.outlined.Air import androidx.compose.material.icons.outlined.ArrowDownward import androidx.compose.material.icons.outlined.ArrowUpward import androidx.compose.material.icons.outlined.Map import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.WaterDrop import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext 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 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 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) ) } } } } 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 } @RequiresApi(Build.VERSION_CODES.O) fun fetchWeather(ctx: Context): ForecastSummary { var todayPlz = getTodaysLocation(ctx) var data: ForecastSummary runBlocking { val forecastResponse = RetrofitClient.instance.getWeatherForecast("${todayPlz},ch", apiKey) val todaysForecast = getTodaysForecast(forecastResponse) data = todaysForecast println("Today's Forecast for ${todayPlz}:") println("Min Temperature: ${todaysForecast.minTemp}°C") println("Max Temperature: ${todaysForecast.maxTemp}°C") println("Rain: ${if (todaysForecast.hasRain) "Yes" else "No"}") println("Snow: ${if (todaysForecast.hasSnow) "Yes" else "No"}") println("Strong Winds: ${if (todaysForecast.hasStrongWinds) "Yes" else "No"}") } return data } fun fetchCurrentTemp(ctx: Context, lat: Double, lon: Double): WeatherResponse { 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 ) 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 } } onDispose { locationHelper.stopListening() } } 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) ) } 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() } } @Composable fun Navigation(icon: ImageVector, iconDescription: String, onClick: () -> Unit = {}) { 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) ) { Icon( imageVector = icon, contentDescription = iconDescription, modifier = Modifier.size(40.dp) ) } } }