diff --git a/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt index fe4b88d..d9a1c54 100644 --- a/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt +++ b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt @@ -1,9 +1,10 @@ package com.henryhiles.qweather.di import com.henryhiles.qweather.domain.remote.WeatherApi -import com.henryhiles.qweather.presentation.screenmodel.AppearanceSettingsScreenModel -import com.henryhiles.qweather.presentation.screenmodel.PreferenceManager -import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel +import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager +import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel +import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel +import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -35,8 +36,9 @@ val appModule = module { } singleOf(::provideWeatherApi) - singleOf(::PreferenceManager) - factoryOf(::AppearanceSettingsScreenModel) + singleOf(::AppearancePreferenceManager) - factoryOf(::WeatherScreenModel) + factoryOf(::AppearancePreferencesScreenModel) + factoryOf(::HourlyWeatherScreenModel) + factoryOf(::DailyWeatherScreenModel) } \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/mappers/WeatherMappers.kt b/app/src/main/java/com/henryhiles/qweather/domain/mappers/WeatherMappers.kt index da2d649..d41e4ba 100644 --- a/app/src/main/java/com/henryhiles/qweather/domain/mappers/WeatherMappers.kt +++ b/app/src/main/java/com/henryhiles/qweather/domain/mappers/WeatherMappers.kt @@ -1,35 +1,56 @@ package com.henryhiles.qweather.domain.mappers -import com.henryhiles.qweather.domain.remote.WeatherDataDto +import com.henryhiles.qweather.domain.remote.DailyWeatherDataDto +import com.henryhiles.qweather.domain.remote.HourlyWeatherDataDto import com.henryhiles.qweather.domain.remote.WeatherDto -import com.henryhiles.qweather.domain.weather.WeatherData -import com.henryhiles.qweather.domain.weather.WeatherInfo +import com.henryhiles.qweather.domain.weather.DailyWeatherData +import com.henryhiles.qweather.domain.weather.HourlyWeatherData +import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo import com.henryhiles.qweather.domain.weather.WeatherType +import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import kotlin.math.roundToInt -private data class IndexedWeatherData(val index: Int, val data: WeatherData) +private data class IndexedHourlyWeatherData(val index: Int, val data: HourlyWeatherData) -fun WeatherDataDto.toWeatherDataMap(): Map> { - return time.mapIndexed { index, time -> - IndexedWeatherData( - index = index, data = WeatherData( +fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): Map> { + return times.mapIndexed { index, time -> + IndexedHourlyWeatherData( + index = index, + data = HourlyWeatherData( time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME), - temperatureCelsius = temperatures[index].toInt(), - pressure = pressures[index], - windSpeed = windSpeeds[index], - humidity = humidities[index], + temperature = temperatures[index].roundToInt(), + apparentTemperature = apparentTemperatures[index].roundToInt(), + windSpeed = windSpeeds[index].roundToInt(), + precipitationProbability = precipitationProbabilities[index], weatherType = WeatherType.fromWMO(weatherCodes[index]) ) ) }.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } } } -fun WeatherDto.toWeatherInfo(): WeatherInfo { - val weatherDataMap = weatherData.toWeatherDataMap() +fun DailyWeatherDataDto.toDailyWeatherDataMap(): List { + return dates.mapIndexed { index, date -> + DailyWeatherData( + date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE), + weatherType = WeatherType.fromWMO(weatherCodes[index]), + apparentTemperatureMax = apparentTemperaturesMax[index].roundToInt(), + apparentTemperatureMin = apparentTemperaturesMin[index].roundToInt(), + temperatureMax = temperaturesMax[index].roundToInt(), + temperatureMin = temperaturesMin[index].roundToInt() + ) + } +} + +fun WeatherDto.toHourlyWeatherInfo(): HourlyWeatherInfo { + val weatherDataMap = hourlyWeatherData.toHourlyWeatherDataMap() val now = LocalDateTime.now() val currentWeatherData = weatherDataMap[0]?.find { it.time.hour == now.hour } - return WeatherInfo(weatherDataPerDay = weatherDataMap, currentWeatherData = currentWeatherData) + return HourlyWeatherInfo( + weatherDataPerDay = weatherDataMap, + currentWeatherData = currentWeatherData + ) } \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/remote/DailyWeatherDataDto.kt b/app/src/main/java/com/henryhiles/qweather/domain/remote/DailyWeatherDataDto.kt new file mode 100644 index 0000000..0f6cf9b --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/remote/DailyWeatherDataDto.kt @@ -0,0 +1,18 @@ +package com.henryhiles.qweather.domain.remote + +import com.squareup.moshi.Json + +data class DailyWeatherDataDto( + @field:Json(name = "time") + val dates: List, + @field:Json(name = "weathercode") + val weatherCodes: List, + @field:Json(name = "temperature_2m_max") + val temperaturesMax: List, + @field:Json(name = "temperature_2m_min") + val temperaturesMin: List, + @field:Json(name = "apparent_temperature_max") + val apparentTemperaturesMax: List, + @field:Json(name = "apparent_temperature_min") + val apparentTemperaturesMin: List +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDataDto.kt b/app/src/main/java/com/henryhiles/qweather/domain/remote/HourlyWeatherDataDto.kt similarity index 51% rename from app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDataDto.kt rename to app/src/main/java/com/henryhiles/qweather/domain/remote/HourlyWeatherDataDto.kt index bf1ac31..9f56652 100644 --- a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDataDto.kt +++ b/app/src/main/java/com/henryhiles/qweather/domain/remote/HourlyWeatherDataDto.kt @@ -2,16 +2,17 @@ package com.henryhiles.qweather.domain.remote import com.squareup.moshi.Json -data class WeatherDataDto( - val time: List, +data class HourlyWeatherDataDto( + @field:Json(name = "time") + val times: List, @field:Json(name = "temperature_2m") val temperatures: List, + @field:Json(name = "apparent_temperature") + val apparentTemperatures: List, @field:Json(name = "weathercode") val weatherCodes: List, - @field:Json(name = "pressure_msl") - val pressures: List, + @field:Json(name = "precipitation_probability") + val precipitationProbabilities: List, @field:Json(name = "windspeed_10m") val windSpeeds: List, - @field:Json(name = "relativehumidity_2m") - val humidities: List ) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherApi.kt b/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherApi.kt index 4c8b913..a6c8876 100644 --- a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherApi.kt +++ b/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherApi.kt @@ -3,10 +3,17 @@ package com.henryhiles.qweather.domain.remote import retrofit2.http.GET import retrofit2.http.Query +const val DAILY = + "daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min" +const val HOURLY = + "hourly=temperature_2m,apparent_temperature,precipitation_probability,weathercode,windspeed_10m" +const val TIMEZONE = "timezone=auto" +const val FORECAST_DAYS = "forecast_days=14" + interface WeatherApi { - @GET("v1/forecast?latitude=43.72&longitude=-79.35&hourly=temperature_2m&daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min&timezone=auto") + @GET("v1/forecast?$HOURLY&$DAILY&$TIMEZONE&$FORECAST_DAYS") suspend fun getWeatherData( @Query("latitude") lat: Double, - @Query("longitude") long: Double + @Query("longitude") long: Double, ): WeatherDto } \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDto.kt b/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDto.kt index 09c1adc..88783a9 100644 --- a/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDto.kt +++ b/app/src/main/java/com/henryhiles/qweather/domain/remote/WeatherDto.kt @@ -4,5 +4,8 @@ import com.squareup.moshi.Json data class WeatherDto( @field:Json(name = "hourly") - val weatherData: WeatherDataDto + val hourlyWeatherData: HourlyWeatherDataDto, + + @field:Json(name = "daily") + val dailyWeatherData: DailyWeatherDataDto ) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt b/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt index cd19b31..c2fe0fa 100644 --- a/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt +++ b/app/src/main/java/com/henryhiles/qweather/domain/repository/WeatherRepository.kt @@ -1,14 +1,35 @@ package com.henryhiles.qweather.domain.repository -import com.henryhiles.qweather.domain.mappers.toWeatherInfo +import com.henryhiles.qweather.domain.mappers.toDailyWeatherDataMap +import com.henryhiles.qweather.domain.mappers.toHourlyWeatherInfo import com.henryhiles.qweather.domain.remote.WeatherApi import com.henryhiles.qweather.domain.util.Resource -import com.henryhiles.qweather.domain.weather.WeatherInfo +import com.henryhiles.qweather.domain.weather.DailyWeatherData +import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo class WeatherRepository constructor(private val api: WeatherApi) { - suspend fun getWeatherData(lat: Double, long: Double): Resource { + suspend fun getHourlyWeatherData(lat: Double, long: Double): Resource { return try { - Resource.Success(data = api.getWeatherData(lat = lat, long = long).toWeatherInfo()) + Resource.Success( + data = api.getWeatherData(lat = lat, long = long).toHourlyWeatherInfo() + ) + } catch (e: Exception) { + e.printStackTrace() + Resource.Error(e.message ?: "An unknown error occurred.") + } + } + + suspend fun getDailyWeatherData( + lat: Double, + long: Double + ): Resource> { + return try { + Resource.Success( + data = api.getWeatherData( + lat = lat, + long = long + ).dailyWeatherData.toDailyWeatherDataMap() + ) } catch (e: Exception) { e.printStackTrace() Resource.Error(e.message ?: "An unknown error occurred.") diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/DailyWeatherData.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/DailyWeatherData.kt new file mode 100644 index 0000000..492c217 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/DailyWeatherData.kt @@ -0,0 +1,12 @@ +package com.henryhiles.qweather.domain.weather + +import java.time.LocalDate + +data class DailyWeatherData( + val date: LocalDate, + val weatherType: WeatherType, + val temperatureMax: Int, + val temperatureMin: Int, + val apparentTemperatureMax: Int, + val apparentTemperatureMin: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherData.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherData.kt new file mode 100644 index 0000000..6dd9617 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherData.kt @@ -0,0 +1,12 @@ +package com.henryhiles.qweather.domain.weather + +import java.time.LocalDateTime + +data class HourlyWeatherData( + val time: LocalDateTime, + val temperature: Int, + val apparentTemperature: Int, + val weatherType: WeatherType, + val precipitationProbability: Int?, + val windSpeed: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherInfo.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherInfo.kt new file mode 100644 index 0000000..87d9255 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/HourlyWeatherInfo.kt @@ -0,0 +1,6 @@ +package com.henryhiles.qweather.domain.weather + +data class HourlyWeatherInfo( + val weatherDataPerDay: Map>, + val currentWeatherData: HourlyWeatherData? +) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt deleted file mode 100644 index c59c18f..0000000 --- a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.henryhiles.qweather.domain.weather - -import java.time.LocalDateTime - -data class WeatherData( - val time: LocalDateTime, - val temperatureCelsius: Int, - val pressure: Double, - val windSpeed: Double, - val humidity: Double, - val weatherType: WeatherType -) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt deleted file mode 100644 index 9d6a991..0000000 --- a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.henryhiles.qweather.domain.weather - -data class WeatherInfo( - val weatherDataPerDay: Map>, - val currentWeatherData: WeatherData? -) \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt b/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt index 5c10988..61763c0 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt @@ -9,13 +9,13 @@ import androidx.compose.material3.Surface import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.SlideTransition import com.henryhiles.qweather.presentation.screen.MainScreen -import com.henryhiles.qweather.presentation.screenmodel.PreferenceManager +import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager import com.henryhiles.qweather.presentation.screenmodel.Theme import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme import org.koin.android.ext.android.inject class QWeatherActivity : ComponentActivity() { - private val prefs: PreferenceManager by inject() + private val prefs: AppearancePreferenceManager by inject() @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt index 719697c..a2c7b37 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt @@ -3,6 +3,8 @@ package com.henryhiles.qweather.presentation.components.weather import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Thermostat import androidx.compose.material3.Card import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -16,14 +18,12 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.henryhiles.qweather.R -import com.henryhiles.qweather.presentation.components.WeatherDataDisplay -import com.henryhiles.qweather.presentation.screenmodel.WeatherState +import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState import java.time.format.DateTimeFormatter -import kotlin.math.roundToInt @Composable -fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) { - state.weatherInfo?.currentWeatherData?.let { +fun WeatherCard(state: HourlyWeatherState, modifier: Modifier = Modifier) { + state.hourlyWeatherInfo?.currentWeatherData?.let { val formattedTime = remember(it) { it.time.format(DateTimeFormatter.ofPattern("HH:mm")) } @@ -48,7 +48,7 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) { modifier = Modifier.width(200.dp) ) Spacer(modifier = Modifier.height(16.dp)) - Text(text = "${it.temperatureCelsius}°C", fontSize = 50.sp) + Text(text = "${it.temperature}°C", fontSize = 50.sp) Spacer(modifier = Modifier.height(16.dp)) Text(text = it.weatherType.weatherDesc, fontSize = 20.sp) Spacer(modifier = Modifier.height(32.dp)) @@ -57,19 +57,19 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) { horizontalArrangement = Arrangement.SpaceAround ) { WeatherDataDisplay( - value = it.pressure.roundToInt(), - unit = "hpa", - icon = ImageVector.vectorResource(id = R.drawable.ic_pressure), - description = "Pressure", + value = it.apparentTemperature, + unit = "°C", + icon = Icons.Default.Thermostat, + description = "Feels like", ) WeatherDataDisplay( - value = it.humidity.roundToInt(), + value = it.precipitationProbability, unit = "%", icon = ImageVector.vectorResource(id = R.drawable.ic_drop), - description = "Humidity" + description = "Chance of precipitation" ) WeatherDataDisplay( - value = it.windSpeed.roundToInt(), + value = it.windSpeed, unit = "km/h", icon = ImageVector.vectorResource(id = R.drawable.ic_wind), description = "Wind Speed", diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt index d808b2f..a0dc036 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt @@ -1,4 +1,4 @@ -package com.henryhiles.qweather.presentation.components +package com.henryhiles.qweather.presentation.components.weather import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,11 +10,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.henryhiles.qweather.R @Composable fun WeatherDataDisplay( - value: Int, + value: Int?, unit: String, icon: ImageVector, description: String, @@ -24,10 +26,9 @@ fun WeatherDataDisplay( Icon( imageVector = icon, contentDescription = description, -// tint = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.size(25.dp) ) Spacer(modifier = Modifier.width(4.dp)) - Text(text = "$value$unit") + Text(text = if (value == null) stringResource(id = R.string.unknown) else "$value$unit") } } \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt index ba1177b..8ef50eb 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt @@ -1,4 +1,4 @@ -package com.henryhiles.qweather.presentation.components +package com.henryhiles.qweather.presentation.components.weather import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -10,13 +10,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.henryhiles.qweather.presentation.components.weather.WeatherHour -import com.henryhiles.qweather.presentation.screenmodel.WeatherState +import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState import java.time.LocalDateTime @Composable -fun WeatherForecast(state: WeatherState, modifier: Modifier = Modifier) { - state.weatherInfo?.weatherDataPerDay?.get(0)?.let { +fun WeatherForecast(state: HourlyWeatherState, modifier: Modifier = Modifier) { + state.hourlyWeatherInfo?.weatherDataPerDay?.get(0)?.let { Column( modifier = modifier .fillMaxWidth() @@ -25,7 +24,7 @@ fun WeatherForecast(state: WeatherState, modifier: Modifier = Modifier) { Text(text = "Today", fontSize = 20.sp, color = Color.White) Spacer(modifier = Modifier.height(16.dp)) val rowState = rememberLazyListState(LocalDateTime.now().hour) - + LazyRow(state = rowState) { items(it) { WeatherHour( diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt index 6c93c81..a01c6fd 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt @@ -11,11 +11,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import com.henryhiles.qweather.domain.weather.WeatherData +import com.henryhiles.qweather.domain.weather.HourlyWeatherData import java.time.format.DateTimeFormatter @Composable -fun WeatherHour(data: WeatherData, modifier: Modifier = Modifier) { +fun WeatherHour(data: HourlyWeatherData, modifier: Modifier = Modifier) { data.let { val formattedTime = remember(it) { it.time.format(DateTimeFormatter.ofPattern("HH:mm")) @@ -29,10 +29,10 @@ fun WeatherHour(data: WeatherData, modifier: Modifier = Modifier) { Text(text = formattedTime) Image( painter = painterResource(id = it.weatherType.iconRes), - contentDescription = "Image of ${data.weatherType.weatherDesc}", + contentDescription = "Image of ${it.weatherType.weatherDesc}", modifier = Modifier.width(40.dp) ) - Text(text = "${it.temperatureCelsius}°C") + Text(text = "${it.temperature}°C") } } diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt index c8e397a..226a6de 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt @@ -16,7 +16,7 @@ import com.henryhiles.qweather.R import com.henryhiles.qweather.presentation.components.LargeToolbar import com.henryhiles.qweather.presentation.components.settings.SettingsItemChoice import com.henryhiles.qweather.presentation.components.settings.SettingsSwitch -import com.henryhiles.qweather.presentation.screenmodel.AppearanceSettingsScreenModel +import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel class AppearanceSettingsScreen : Screen { @@ -25,7 +25,7 @@ class AppearanceSettingsScreen : Screen { @Composable private fun Screen( - screenModel: AppearanceSettingsScreenModel = getScreenModel() + screenModel: AppearancePreferencesScreenModel = getScreenModel() ) { val ctx = LocalContext.current diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearancePreferencesScreenModel.kt similarity index 83% rename from app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt rename to app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearancePreferencesScreenModel.kt index cd6d3e0..a40a274 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearancePreferencesScreenModel.kt @@ -7,7 +7,7 @@ import cafe.adriel.voyager.core.model.ScreenModel import com.henryhiles.qweather.R import com.henryhiles.qweather.domain.manager.BasePreferenceManager -class AppearanceSettingsManager(context: Context) : +class AppearancePreferenceManager(context: Context) : BasePreferenceManager(context.getSharedPreferences("prefs", Context.MODE_PRIVATE)) { var theme by enumPreference("theme", Theme.SYSTEM) var monet by booleanPreference("monet", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) @@ -19,6 +19,6 @@ enum class Theme(@StringRes val label: Int) { DARK(R.string.theme_dark); } -class AppearanceSettingsScreenModel( - val prefs: AppearanceSettingsManager +class AppearancePreferencesScreenModel( + val prefs: AppearancePreferenceManager ) : ScreenModel \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/DailyWeatherScreenModel.kt similarity index 79% rename from app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt rename to app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/DailyWeatherScreenModel.kt index 2061e89..f1a48fc 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/DailyWeatherScreenModel.kt @@ -8,20 +8,20 @@ import cafe.adriel.voyager.core.model.coroutineScope import com.henryhiles.qweather.domain.location.LocationTracker import com.henryhiles.qweather.domain.repository.WeatherRepository import com.henryhiles.qweather.domain.util.Resource -import com.henryhiles.qweather.domain.weather.WeatherInfo +import com.henryhiles.qweather.domain.weather.DailyWeatherData import kotlinx.coroutines.launch -data class WeatherState( - val weatherInfo: WeatherInfo? = null, +data class DailyWeatherState( + val dailyWeatherData: List? = null, val isLoading: Boolean = false, val error: String? = null ) -class WeatherScreenModel constructor( +class DailyWeatherScreenModel constructor( private val repository: WeatherRepository, private val locationTracker: LocationTracker, ) : ScreenModel { - var state by mutableStateOf(WeatherState()) + var state by mutableStateOf(DailyWeatherState()) private set fun loadWeatherInfo() { @@ -30,10 +30,10 @@ class WeatherScreenModel constructor( val currentLocation = locationTracker.getCurrentLocation() currentLocation?.let { location -> state = when (val result = - repository.getWeatherData(location.latitude, location.longitude)) { + repository.getDailyWeatherData(location.latitude, location.longitude)) { is Resource.Success -> { state.copy( - weatherInfo = result.data, + dailyWeatherData = result.data, isLoading = false, error = null ) @@ -41,7 +41,7 @@ class WeatherScreenModel constructor( is Resource.Error -> { state.copy( - weatherInfo = null, + dailyWeatherData = null, isLoading = false, error = result.message ) diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/HourlyWeatherScreenModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/HourlyWeatherScreenModel.kt new file mode 100644 index 0000000..19d18f4 --- /dev/null +++ b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/HourlyWeatherScreenModel.kt @@ -0,0 +1,58 @@ +package com.henryhiles.qweather.presentation.screenmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import com.henryhiles.qweather.domain.location.LocationTracker +import com.henryhiles.qweather.domain.repository.WeatherRepository +import com.henryhiles.qweather.domain.util.Resource +import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo +import kotlinx.coroutines.launch + +data class HourlyWeatherState( + val hourlyWeatherInfo: HourlyWeatherInfo? = null, + val isLoading: Boolean = false, + val error: String? = null +) + +class HourlyWeatherScreenModel constructor( + private val repository: WeatherRepository, + private val locationTracker: LocationTracker, +) : ScreenModel { + var state by mutableStateOf(HourlyWeatherState()) + private set + + fun loadWeatherInfo() { + coroutineScope.launch { + state = state.copy(isLoading = true, error = null) + val currentLocation = locationTracker.getCurrentLocation() + currentLocation?.let { location -> + state = when (val result = + repository.getHourlyWeatherData(location.latitude, location.longitude)) { + is Resource.Success -> { + state.copy( + hourlyWeatherInfo = result.data, + isLoading = false, + error = null + ) + } + + is Resource.Error -> { + state.copy( + hourlyWeatherInfo = null, + isLoading = false, + error = result.message + ) + } + } + } ?: kotlin.run { + state = state.copy( + isLoading = false, + error = "Couldn't retrieve location. Make sure to grant permission and enable GPS." + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt index b029407..5f60162 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt @@ -5,7 +5,10 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home -import androidx.compose.material3.* +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -21,8 +24,8 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberPermissionState import com.henryhiles.qweather.R import com.henryhiles.qweather.presentation.components.weather.WeatherCard -import com.henryhiles.qweather.presentation.components.WeatherForecast -import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel +import com.henryhiles.qweather.presentation.components.weather.WeatherForecast +import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel object TodayTab : Tab { override val options: TabOptions @@ -43,7 +46,7 @@ object TodayTab : Tab { @OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class) @Composable override fun Content() { - val weatherViewModel = getScreenModel() + val weatherViewModel = getScreenModel() val permissionsState = rememberPermissionState( Manifest.permission.ACCESS_FINE_LOCATION, @@ -65,27 +68,16 @@ object TodayTab : Tab { ) } weatherViewModel.state.error != null -> { - AlertDialog(onDismissRequest = {}) { - Surface( - shape = MaterialTheme.shapes.large - ) { - Column(modifier = Modifier.padding(16.dp)) { + AlertDialog( + onDismissRequest = {}, + confirmButton = {}, + title = { Text(text = "An error occurred") }, text = { + SelectionContainer { Text( - text = "An error occurred", - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.align(Alignment.CenterHorizontally) + text = weatherViewModel.state.error!!, ) - - SelectionContainer { - Text( - text = weatherViewModel.state.error!!, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(16.dp) - ) - } } - } - } + }) } else -> { Column( diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt index 34c6e1d..746fd67 100644 --- a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt +++ b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt @@ -1,15 +1,30 @@ package com.henryhiles.qweather.presentation.tabs +import android.Manifest +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DateRange -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabOptions +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.rememberPermissionState import com.henryhiles.qweather.R +import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel object WeekTab : Tab { override val options: TabOptions @@ -27,8 +42,91 @@ object WeekTab : Tab { } } + @OptIn(ExperimentalPermissionsApi::class) @Composable override fun Content() { - Text(text = "Week Screen") + val weatherViewModel = getScreenModel() + + val permissionsState = rememberPermissionState( + Manifest.permission.ACCESS_FINE_LOCATION, + ) { + weatherViewModel.loadWeatherInfo() + } + + LaunchedEffect(key1 = true) { + permissionsState.launchPermissionRequest() + } + + Box(modifier = Modifier.fillMaxSize()) { + when { + weatherViewModel.state.isLoading -> { + CircularProgressIndicator( + modifier = Modifier.align( + Alignment.Center + ) + ) + } + weatherViewModel.state.error != null -> { + AlertDialog( + onDismissRequest = {}, + confirmButton = {}, + title = { Text(text = "An error occurred") }, + text = { + SelectionContainer { + Text( + text = weatherViewModel.state.error!!, + ) + } + }) + } + else -> { + LazyColumn( + modifier = Modifier + .fillMaxSize() + ) { + weatherViewModel.state.dailyWeatherData?.let { data -> + items(data) { + Card( + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = 8.dp + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = it.weatherType.iconRes), + contentDescription = "Image of ${it.weatherType}", + modifier = Modifier.width(48.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = //it.date.dayOfWeek + when (it.date.dayOfWeek.value) { + 1 -> "Monday" + 2 -> "Tuesday" + 3 -> "Wednesday" + 4 -> "Thursday" + 5 -> "Friday" + 6 -> "Saturday" + 7 -> "Sunday" + else -> "Unknown" + } + ) + Spacer(modifier = Modifier.width(24.dp)) + Text(text = it.temperatureMax.toString()) + } + } + } + } + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff2081b..fdd4c78 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,4 +17,6 @@ System Light Dark + + Unknown \ No newline at end of file