Different unit support, fixes #2
This commit is contained in:
parent
985382fa1b
commit
ae71058669
23 changed files with 304 additions and 67 deletions
|
@ -1,6 +1,7 @@
|
|||
package com.henryhiles.qweather.di
|
||||
|
||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
||||
import com.henryhiles.qweather.presentation.screenmodel.UnitPreferenceManager
|
||||
import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManager
|
||||
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
|
@ -8,5 +9,6 @@ import org.koin.dsl.module
|
|||
|
||||
val managerModule = module {
|
||||
singleOf(::AppearancePreferenceManager)
|
||||
singleOf(::UnitPreferenceManager)
|
||||
singleOf(::LocationPreferenceManager)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.henryhiles.qweather.di
|
||||
|
||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
||||
import com.henryhiles.qweather.presentation.screenmodel.UnitPreferencesScreenModel
|
||||
import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel
|
||||
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel
|
||||
import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel
|
||||
|
@ -9,6 +10,7 @@ import org.koin.dsl.module
|
|||
|
||||
val screenModelModule = module {
|
||||
factoryOf(::AppearancePreferencesScreenModel)
|
||||
factoryOf(::UnitPreferencesScreenModel)
|
||||
factoryOf(::LocationPickerScreenModel)
|
||||
factoryOf(::HourlyWeatherScreenModel)
|
||||
factoryOf(::DailyWeatherScreenModel)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.henryhiles.qweather.domain.mappers
|
||||
|
||||
import com.henryhiles.qweather.domain.remote.DailyWeatherDataDto
|
||||
import com.henryhiles.qweather.domain.remote.DailyWeatherUnitsDto
|
||||
import com.henryhiles.qweather.domain.remote.HourlyWeatherDataDto
|
||||
import com.henryhiles.qweather.domain.remote.HourlyWeatherUnitsDto
|
||||
import com.henryhiles.qweather.domain.remote.WeatherDto
|
||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||
import com.henryhiles.qweather.domain.weather.HourlyWeatherData
|
||||
|
@ -12,7 +14,7 @@ import java.time.LocalDateTime
|
|||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun HourlyWeatherDataDto.toHourlyWeatherData(): List<HourlyWeatherData> {
|
||||
fun HourlyWeatherDataDto.toHourlyWeatherData(units: HourlyWeatherUnitsDto): List<HourlyWeatherData> {
|
||||
return time.subList(0, 24).mapIndexed { index, time ->
|
||||
HourlyWeatherData(
|
||||
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
||||
|
@ -21,11 +23,12 @@ fun HourlyWeatherDataDto.toHourlyWeatherData(): List<HourlyWeatherData> {
|
|||
windSpeed = windSpeed[index].roundToInt(),
|
||||
precipitationProbability = precipitationProbability.getOrNull(index),
|
||||
weatherType = WeatherType.fromWMO(weatherCode[index]),
|
||||
units = units,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun DailyWeatherDataDto.toDailyWeatherData(): List<DailyWeatherData> {
|
||||
fun DailyWeatherDataDto.toDailyWeatherData(units: DailyWeatherUnitsDto): List<DailyWeatherData> {
|
||||
return date.mapIndexed { index, date ->
|
||||
DailyWeatherData(
|
||||
date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
|
||||
|
@ -38,12 +41,17 @@ fun DailyWeatherDataDto.toDailyWeatherData(): List<DailyWeatherData> {
|
|||
windSpeedMax = windSpeedMax[index].roundToInt(),
|
||||
sunrise = LocalDateTime.parse(sunrise[index]),
|
||||
sunset = LocalDateTime.parse(sunset[index]),
|
||||
precipitationSum = precipitationSum[index],
|
||||
units = units.copy(
|
||||
precipitationSum = units.precipitationSum.replace("inch", "\""),
|
||||
windSpeedMax = units.windSpeedMax.replace("mp/h", "mph"),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun WeatherDto.toHourlyWeatherInfo(): HourlyWeatherInfo {
|
||||
val weatherDataMap = hourlyWeatherData.toHourlyWeatherData()
|
||||
val weatherDataMap = hourlyWeatherData.toHourlyWeatherData(units = hourlyUnits)
|
||||
val now = LocalDateTime.now()
|
||||
val currentWeatherData = weatherDataMap.find {
|
||||
it.time.hour == now.hour
|
||||
|
|
|
@ -25,4 +25,21 @@ data class DailyWeatherDataDto(
|
|||
val apparentTemperatureMax: List<Float>,
|
||||
@SerialName("apparent_temperature_min")
|
||||
val apparentTemperatureMin: List<Float>
|
||||
)
|
||||
@Serializable
|
||||
data class DailyWeatherUnitsDto(
|
||||
@SerialName("precipitation_probability_max")
|
||||
val precipitationProbabilityMax: String,
|
||||
@SerialName("precipitation_sum")
|
||||
val precipitationSum: String,
|
||||
@SerialName("windspeed_10m_max")
|
||||
val windSpeedMax: String,
|
||||
@SerialName("temperature_2m_max")
|
||||
val temperatureMax: String,
|
||||
@SerialName("temperature_2m_min")
|
||||
val temperatureMin: String,
|
||||
@SerialName("apparent_temperature_max")
|
||||
val apparentTemperatureMax: String,
|
||||
@SerialName("apparent_temperature_min")
|
||||
val apparentTemperatureMin: String
|
||||
)
|
|
@ -16,4 +16,16 @@ data class HourlyWeatherDataDto(
|
|||
val precipitationProbability: List<Int?>,
|
||||
@SerialName("windspeed_10m")
|
||||
val windSpeed: List<Float>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HourlyWeatherUnitsDto(
|
||||
@SerialName("temperature_2m")
|
||||
val temperature: String,
|
||||
@SerialName("apparent_temperature")
|
||||
val apparentTemperature: String,
|
||||
@SerialName("precipitation_probability")
|
||||
val precipitationProbability: String,
|
||||
@SerialName("windspeed_10m")
|
||||
val windSpeed: String,
|
||||
)
|
|
@ -1,5 +1,7 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.henryhiles.qweather.presentation.screenmodel.PrecipitationUnit
|
||||
import com.henryhiles.qweather.presentation.screenmodel.TempUnit
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Query
|
||||
|
@ -17,6 +19,9 @@ interface WeatherApi {
|
|||
suspend fun getWeatherData(
|
||||
@Query("latitude") lat: Float,
|
||||
@Query("longitude") long: Float,
|
||||
@Query("temperature_unit") tempUnit: String,
|
||||
@Query("wind_speed_unit") windUnit: String,
|
||||
@Query("precipitation_unit") precipitationUnit: String,
|
||||
): WeatherDto
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
|
@ -24,5 +29,8 @@ interface WeatherApi {
|
|||
suspend fun getWeatherDataWithoutCache(
|
||||
@Query("latitude") lat: Float,
|
||||
@Query("longitude") long: Float,
|
||||
@Query("temperature_unit") tempUnit: String,
|
||||
@Query("wind_speed_unit") windUnit: String,
|
||||
@Query("precipitation_unit") precipitationUnit: String,
|
||||
): WeatherDto
|
||||
}
|
|
@ -8,6 +8,12 @@ data class WeatherDto(
|
|||
@SerialName("hourly")
|
||||
val hourlyWeatherData: HourlyWeatherDataDto,
|
||||
|
||||
@SerialName("hourly_units")
|
||||
val hourlyUnits: HourlyWeatherUnitsDto,
|
||||
|
||||
@SerialName("daily")
|
||||
val dailyWeatherData: DailyWeatherDataDto
|
||||
val dailyWeatherData: DailyWeatherDataDto,
|
||||
|
||||
@SerialName("daily_units")
|
||||
val dailyUnits: DailyWeatherUnitsDto,
|
||||
)
|
|
@ -1,28 +1,48 @@
|
|||
package com.henryhiles.qweather.domain.repository
|
||||
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import com.henryhiles.qweather.domain.mappers.toDailyWeatherData
|
||||
import com.henryhiles.qweather.domain.mappers.toHourlyWeatherInfo
|
||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||
import com.henryhiles.qweather.domain.remote.WeatherDto
|
||||
import com.henryhiles.qweather.domain.util.Resource
|
||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||
import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo
|
||||
import com.henryhiles.qweather.presentation.screenmodel.UnitPreferenceManager
|
||||
|
||||
class WeatherRepository(private val api: WeatherApi) {
|
||||
suspend fun getHourlyWeatherData(
|
||||
private suspend fun getWeatherData(
|
||||
lat: Float,
|
||||
long: Float,
|
||||
units: UnitPreferenceManager,
|
||||
cache: Boolean = true,
|
||||
): WeatherDto {
|
||||
return if (cache) api.getWeatherData(
|
||||
lat,
|
||||
long,
|
||||
units.tempUnit.name.lowercase(),
|
||||
units.windUnit.name.lowercase(),
|
||||
units.precipitationUnit.name.lowercase(),
|
||||
) else api.getWeatherDataWithoutCache(
|
||||
lat,
|
||||
long,
|
||||
units.tempUnit.name,
|
||||
units.windUnit.name,
|
||||
units.precipitationUnit.name,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getDailyWeatherData(
|
||||
lat: Float,
|
||||
long: Float,
|
||||
units: UnitPreferenceManager,
|
||||
cache: Boolean = true
|
||||
): Resource<HourlyWeatherInfo> {
|
||||
): Resource<List<DailyWeatherData>> {
|
||||
return try {
|
||||
Resource.Success(
|
||||
data = (
|
||||
if (cache) api.getWeatherData(
|
||||
lat = lat,
|
||||
long = long
|
||||
) else api.getWeatherDataWithoutCache(
|
||||
lat = lat,
|
||||
long = long
|
||||
)).toHourlyWeatherInfo()
|
||||
with(getWeatherData(lat, long, units, cache)) {
|
||||
dailyWeatherData.toDailyWeatherData(dailyUnits)
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
@ -30,20 +50,15 @@ class WeatherRepository(private val api: WeatherApi) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getDailyWeatherData(
|
||||
suspend fun getHourlyWeatherData(
|
||||
lat: Float,
|
||||
long: Float,
|
||||
units: UnitPreferenceManager,
|
||||
cache: Boolean = true
|
||||
): Resource<List<DailyWeatherData>> {
|
||||
): Resource<HourlyWeatherInfo> {
|
||||
return try {
|
||||
Resource.Success(
|
||||
(if (cache) api.getWeatherData(
|
||||
lat = lat,
|
||||
long = long
|
||||
) else api.getWeatherDataWithoutCache(
|
||||
lat = lat,
|
||||
long = long
|
||||
)).dailyWeatherData.toDailyWeatherData()
|
||||
getWeatherData(lat, long, units, cache).toHourlyWeatherInfo()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.henryhiles.qweather.domain.weather
|
||||
|
||||
import com.henryhiles.qweather.domain.remote.DailyWeatherUnitsDto
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class DailyWeatherData(
|
||||
val date: LocalDate,
|
||||
val precipitationSum: Float,
|
||||
val weatherType: WeatherType,
|
||||
val sunrise: LocalDateTime,
|
||||
val sunset: LocalDateTime,
|
||||
|
@ -13,5 +15,6 @@ data class DailyWeatherData(
|
|||
val apparentTemperatureMax: Int,
|
||||
val apparentTemperatureMin: Int,
|
||||
val precipitationProbabilityMax: Int?,
|
||||
val windSpeedMax: Int
|
||||
val windSpeedMax: Int,
|
||||
val units: DailyWeatherUnitsDto,
|
||||
)
|
|
@ -1,5 +1,6 @@
|
|||
package com.henryhiles.qweather.domain.weather
|
||||
|
||||
import com.henryhiles.qweather.domain.remote.HourlyWeatherUnitsDto
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class HourlyWeatherData(
|
||||
|
@ -9,4 +10,5 @@ data class HourlyWeatherData(
|
|||
val weatherType: WeatherType,
|
||||
val precipitationProbability: Int?,
|
||||
val windSpeed: Int,
|
||||
val units: HourlyWeatherUnitsDto,
|
||||
)
|
|
@ -9,6 +9,7 @@ import androidx.compose.ui.Modifier
|
|||
@Composable
|
||||
inline fun <reified E : Enum<E>> SettingsItemChoice(
|
||||
label: String,
|
||||
secondaryLabel: String? = null,
|
||||
title: String = label,
|
||||
disabled: Boolean = false,
|
||||
pref: E,
|
||||
|
@ -23,6 +24,7 @@ inline fun <reified E : Enum<E>> SettingsItemChoice(
|
|||
SettingItem(
|
||||
modifier = Modifier.clickable { opened = true },
|
||||
text = { Text(text = label) },
|
||||
secondaryText = { if (secondaryLabel != null) Text(text = secondaryLabel) }
|
||||
) {
|
||||
SettingsChoiceDialog(
|
||||
visible = opened,
|
||||
|
|
|
@ -15,15 +15,22 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.util.getIcon
|
||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||
import com.henryhiles.qweather.domain.weather.HourlyWeatherData
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Composable
|
||||
fun WeatherCard(hour: HourlyWeatherData, dailyData: DailyWeatherData, modifier: Modifier = Modifier) {
|
||||
fun WeatherCard(
|
||||
hour: HourlyWeatherData,
|
||||
dailyData: DailyWeatherData,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val formattedTime = remember(hour) {
|
||||
hour.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||
}
|
||||
|
@ -48,14 +55,23 @@ fun WeatherCard(hour: HourlyWeatherData, dailyData: DailyWeatherData, modifier:
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Image(
|
||||
painter = painterResource(id = getIcon(hour, dailyData)),
|
||||
contentDescription = "Image of ${hour.weatherType.weatherDesc}",
|
||||
contentDescription = hour.weatherType.weatherDesc,
|
||||
modifier = Modifier.height(140.dp),
|
||||
contentScale = ContentScale.FillHeight
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(text = "${hour.temperature}°C", fontSize = 50.sp)
|
||||
Text(text = "${hour.temperature}${hour.units.temperature}", fontSize = 50.sp)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(text = "${hour.weatherType.weatherDesc} - Feels like ${hour.apparentTemperature}°C", fontSize = 20.sp)
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.weather_description,
|
||||
hour.weatherType.weatherDesc,
|
||||
hour.apparentTemperature,
|
||||
hour.units.apparentTemperature
|
||||
),
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
@ -63,15 +79,15 @@ fun WeatherCard(hour: HourlyWeatherData, dailyData: DailyWeatherData, modifier:
|
|||
) {
|
||||
WeatherDataDisplay(
|
||||
value = hour.precipitationProbability,
|
||||
unit = "%",
|
||||
unit = hour.units.precipitationProbability,
|
||||
icon = Icons.Outlined.WaterDrop,
|
||||
description = "Chance of precipitation"
|
||||
description = stringResource(id = R.string.precipitation_probability)
|
||||
)
|
||||
WeatherDataDisplay(
|
||||
value = hour.windSpeed,
|
||||
unit = "km/h",
|
||||
unit = hour.units.windSpeed,
|
||||
icon = Icons.Outlined.WindPower,
|
||||
description = "Wind Speed",
|
||||
description = stringResource(id = R.string.wind_speed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import com.henryhiles.qweather.R
|
|||
|
||||
@Composable
|
||||
fun WeatherDataDisplay(
|
||||
value: Int?,
|
||||
value: Any?,
|
||||
unit: String,
|
||||
icon: ImageVector,
|
||||
description: String,
|
||||
|
|
|
@ -16,8 +16,10 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
@ -40,7 +42,7 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
|||
) {
|
||||
Image(
|
||||
painter = painterResource(id = dailyWeatherData.weatherType.iconRes),
|
||||
contentDescription = "Image of ${dailyWeatherData.weatherType.weatherDesc}",
|
||||
contentDescription = dailyWeatherData.weatherType.weatherDesc,
|
||||
modifier = Modifier.width(48.dp)
|
||||
)
|
||||
|
||||
|
@ -50,12 +52,18 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
|||
text = formattedDate,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(text = "Feels like ${dailyWeatherData.apparentTemperatureMax}°C")
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.feels_like,
|
||||
dailyWeatherData.apparentTemperatureMax,
|
||||
dailyWeatherData.units.apparentTemperatureMax
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = "${dailyWeatherData.temperatureMax}°C",
|
||||
text = "${dailyWeatherData.temperatureMax}${dailyWeatherData.units.temperatureMax}",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
|
@ -67,23 +75,21 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
|||
) {
|
||||
WeatherDataDisplay(
|
||||
value = dailyWeatherData.precipitationProbabilityMax,
|
||||
unit = "%",
|
||||
unit = dailyWeatherData.units.precipitationProbabilityMax,
|
||||
icon = Icons.Outlined.WaterDrop,
|
||||
description = "Chance of rain"
|
||||
description = stringResource(id = R.string.precipitation_probability)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
WeatherDataDisplay(
|
||||
value = dailyWeatherData.windSpeedMax,
|
||||
unit = "mm",
|
||||
value = dailyWeatherData.precipitationSum,
|
||||
unit = dailyWeatherData.units.precipitationSum,
|
||||
icon = Icons.Outlined.Water,
|
||||
description = "Precipitation Amount"
|
||||
description = stringResource(id = R.string.precipitation_amount)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
WeatherDataDisplay(
|
||||
value = dailyWeatherData.windSpeedMax,
|
||||
unit = "km/h",
|
||||
unit = dailyWeatherData.units.windSpeedMax,
|
||||
icon = Icons.Outlined.WindPower,
|
||||
description = "Wind Speed"
|
||||
description = stringResource(id = R.string.wind_speed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,10 +44,10 @@ fun WeatherHour(
|
|||
painter = painterResource(
|
||||
id = getIcon(it, dailyData)
|
||||
),
|
||||
contentDescription = "Image of ${it.weatherType.weatherDesc}",
|
||||
contentDescription = it.weatherType.weatherDesc,
|
||||
modifier = Modifier.width(40.dp)
|
||||
)
|
||||
Text(text = "${it.temperature}°C")
|
||||
Text(text = "${it.temperature}${it.units.temperature}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,19 +46,19 @@ fun WeatherToday(data: DailyWeatherData) {
|
|||
) {
|
||||
WeatherDataDisplay(
|
||||
value = data.temperatureMax,
|
||||
unit = "°C",
|
||||
unit = data.units.temperatureMax,
|
||||
icon = Icons.Default.ArrowUpward,
|
||||
description = stringResource(R.string.weather_high, data.temperatureMax)
|
||||
description = stringResource(R.string.weather_high, data.temperatureMax, data.units.temperatureMax)
|
||||
)
|
||||
WeatherDataDisplay(
|
||||
value = data.temperatureMin,
|
||||
unit = "°C",
|
||||
unit = data.units.temperatureMin,
|
||||
icon = Icons.Default.ArrowDownward,
|
||||
description = stringResource(id = R.string.weather_low, data.temperatureMin)
|
||||
description = stringResource(id = R.string.weather_low, data.temperatureMin, data.units.temperatureMin)
|
||||
)
|
||||
WeatherDataDisplay(
|
||||
value = data.precipitationProbabilityMax,
|
||||
unit = "%",
|
||||
unit = data.units.precipitationProbabilityMax,
|
||||
icon = Icons.Outlined.WaterDrop,
|
||||
description = data.precipitationProbabilityMax?.let {
|
||||
stringResource(
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.henryhiles.qweather.presentation.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.presentation.components.navigation.SmallToolbar
|
||||
import com.henryhiles.qweather.presentation.components.settings.SettingsItemChoice
|
||||
import com.henryhiles.qweather.presentation.screenmodel.UnitPreferencesScreenModel
|
||||
|
||||
class UnitsScreen : Screen {
|
||||
@Composable
|
||||
override fun Content(){
|
||||
val screenModel: UnitPreferencesScreenModel = getScreenModel()
|
||||
val context = LocalContext.current
|
||||
|
||||
Scaffold(topBar = {
|
||||
SmallToolbar(
|
||||
title = { Text(text = stringResource(R.string.settings_units)) },
|
||||
backButton = true
|
||||
)
|
||||
}) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SettingsItemChoice(
|
||||
label = stringResource(R.string.unit_temp),
|
||||
secondaryLabel = stringResource(R.string.unit_temp_desc),
|
||||
pref = screenModel.prefs.tempUnit,
|
||||
labelFactory = { context.getString(it.label) }
|
||||
) { screenModel.prefs.tempUnit = it }
|
||||
|
||||
SettingsItemChoice(
|
||||
label = stringResource(R.string.unit_wind),
|
||||
secondaryLabel = stringResource(R.string.unit_wind_desc),
|
||||
pref = screenModel.prefs.windUnit,
|
||||
labelFactory = { context.getString(it.label) }
|
||||
) { screenModel.prefs.windUnit = it }
|
||||
|
||||
SettingsItemChoice(
|
||||
label = stringResource(R.string.unit_precipitation),
|
||||
secondaryLabel = stringResource(R.string.unit_precipitation_desc),
|
||||
pref = screenModel.prefs.precipitationUnit,
|
||||
labelFactory = { context.getString(it.label) }
|
||||
) { screenModel.prefs.precipitationUnit = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@ data class DailyWeatherState(
|
|||
|
||||
class DailyWeatherScreenModel(
|
||||
private val repository: WeatherRepository,
|
||||
val locationPreferenceManager: LocationPreferenceManager
|
||||
private val unitsPreferenceManager: UnitPreferenceManager,
|
||||
val locationPreferenceManager: LocationPreferenceManager,
|
||||
) : ScreenModel {
|
||||
var state by mutableStateOf(DailyWeatherState())
|
||||
private set
|
||||
|
@ -31,7 +32,8 @@ class DailyWeatherScreenModel(
|
|||
state = when (val result = repository.getDailyWeatherData(
|
||||
lat = location.latitude,
|
||||
long = location.longitude,
|
||||
cache = cache
|
||||
cache = cache,
|
||||
units = unitsPreferenceManager,
|
||||
)) {
|
||||
is Resource.Success -> {
|
||||
state.copy(
|
||||
|
|
|
@ -19,6 +19,7 @@ data class HourlyWeatherState(
|
|||
|
||||
class HourlyWeatherScreenModel(
|
||||
private val repository: WeatherRepository,
|
||||
private val unitsPreferenceManager: UnitPreferenceManager,
|
||||
val locationPreferenceManager: LocationPreferenceManager,
|
||||
) : ScreenModel {
|
||||
var state by mutableStateOf(HourlyWeatherState())
|
||||
|
@ -32,6 +33,7 @@ class HourlyWeatherScreenModel(
|
|||
repository.getHourlyWeatherData(
|
||||
lat = location.latitude,
|
||||
long = location.longitude,
|
||||
units = unitsPreferenceManager,
|
||||
cache = cache
|
||||
)) {
|
||||
is Resource.Success -> {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.henryhiles.qweather.presentation.screenmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.manager.BasePreferenceManager
|
||||
|
||||
class UnitPreferenceManager(context: Context) :
|
||||
BasePreferenceManager(context.getSharedPreferences("prefs", Context.MODE_PRIVATE)) {
|
||||
|
||||
var tempUnit by enumPreference("temp_unit", TempUnit.CELSIUS)
|
||||
var windUnit by enumPreference("wind_unit", WindUnit.KMH)
|
||||
var precipitationUnit by enumPreference("precipitation_unit", PrecipitationUnit.MM)
|
||||
}
|
||||
|
||||
enum class TempUnit(@StringRes val label: Int) {
|
||||
CELSIUS(R.string.celsius),
|
||||
FAHRENHEIT(R.string.fahrenheit),
|
||||
}
|
||||
|
||||
enum class WindUnit(@StringRes val label: Int) {
|
||||
KMH(R.string.kmh),
|
||||
MS(R.string.ms),
|
||||
MPH(R.string.mph),
|
||||
KN(R.string.kn),
|
||||
}
|
||||
|
||||
enum class PrecipitationUnit(@StringRes val label: Int) {
|
||||
MM(R.string.mm),
|
||||
INCH(R.string.inch)
|
||||
}
|
||||
|
||||
class UnitPreferencesScreenModel(
|
||||
val prefs: UnitPreferenceManager
|
||||
) : ScreenModel
|
|
@ -3,6 +3,7 @@ package com.henryhiles.qweather.presentation.tabs
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.outlined.AcUnit
|
||||
import androidx.compose.material.icons.outlined.GpsFixed
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.Palette
|
||||
|
@ -20,6 +21,7 @@ import com.henryhiles.qweather.presentation.components.settings.SettingsCategory
|
|||
import com.henryhiles.qweather.presentation.screen.AboutScreen
|
||||
import com.henryhiles.qweather.presentation.screen.AppearanceSettingsScreen
|
||||
import com.henryhiles.qweather.presentation.screen.LocationPickerScreen
|
||||
import com.henryhiles.qweather.presentation.screen.UnitsScreen
|
||||
|
||||
object SettingsTab : NavigationTab {
|
||||
override val options: TabOptions
|
||||
|
@ -52,6 +54,12 @@ object SettingsTab : NavigationTab {
|
|||
subtext = stringResource(R.string.settings_location_description),
|
||||
destination = ::LocationPickerScreen
|
||||
)
|
||||
SettingsCategory(
|
||||
icon = Icons.Outlined.AcUnit,
|
||||
text = stringResource(R.string.settings_units),
|
||||
subtext = stringResource(R.string.settings_units_description),
|
||||
destination = ::UnitsScreen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,15 +46,15 @@ object WeekTab : NavigationTab {
|
|||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val dailyWeatherViewModel = getScreenModel<DailyWeatherScreenModel>()
|
||||
val weatherViewModel = getScreenModel<DailyWeatherScreenModel>()
|
||||
|
||||
LaunchedEffect(key1 = dailyWeatherViewModel.locationPreferenceManager.selectedIndex) {
|
||||
dailyWeatherViewModel.loadWeatherInfo()
|
||||
LaunchedEffect(key1 = weatherViewModel.locationPreferenceManager.selectedIndex) {
|
||||
weatherViewModel.loadWeatherInfo()
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
when {
|
||||
dailyWeatherViewModel.state.isLoading -> {
|
||||
weatherViewModel.state.isLoading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(
|
||||
Alignment.Center
|
||||
|
@ -62,15 +62,19 @@ object WeekTab : NavigationTab {
|
|||
)
|
||||
}
|
||||
|
||||
dailyWeatherViewModel.state.error != null -> {
|
||||
weatherViewModel.state.error != null -> {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
title = { Text(text = stringResource(R.string.error)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { weatherViewModel.loadWeatherInfo() }) {
|
||||
Text(text = stringResource(id = R.string.action_try_again))
|
||||
}
|
||||
},
|
||||
title = { Text(text = stringResource(id = R.string.error)) },
|
||||
text = {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = dailyWeatherViewModel.state.error!!,
|
||||
text = weatherViewModel.state.error!!,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -79,7 +83,7 @@ object WeekTab : NavigationTab {
|
|||
|
||||
else -> {
|
||||
LazyColumn(contentPadding = PaddingValues(16.dp)) {
|
||||
dailyWeatherViewModel.state.dailyWeatherData?.let { data ->
|
||||
weatherViewModel.state.dailyWeatherData?.let { data ->
|
||||
item { WeatherToday(data = data[0]) }
|
||||
items(data) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
@ -94,10 +98,10 @@ object WeekTab : NavigationTab {
|
|||
|
||||
@Composable
|
||||
override fun Actions() {
|
||||
val dailyWeatherViewModel = getScreenModel<DailyWeatherScreenModel>()
|
||||
val weatherViewModel = getScreenModel<DailyWeatherScreenModel>()
|
||||
|
||||
IconButton(onClick = {
|
||||
dailyWeatherViewModel.loadWeatherInfo(cache = false)
|
||||
weatherViewModel.loadWeatherInfo(cache = false)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
<string name="settings_appearance_description">Theme, dynamic colors</string>
|
||||
<string name="settings_location">Location</string>
|
||||
<string name="settings_location_description">Location to fetch data from</string>
|
||||
<string name="settings_units">Units</string>
|
||||
<string name="settings_units_description">Units to fetch data in, e.g. imperial/metric</string>
|
||||
|
||||
<string name="location">Location</string>
|
||||
<string name="locations">Locations</string>
|
||||
|
@ -34,9 +36,33 @@
|
|||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
|
||||
<string name="weather_high">High: %1$d°C</string>
|
||||
<string name="weather_low">Low: %1$d°C</string>
|
||||
|
||||
<string name="unit_temp">Temperature Unit</string>
|
||||
<string name="celsius">Celsius (C°)</string>
|
||||
<string name="fahrenheit">Fahrenheit (F°)</string>
|
||||
<string name="unit_temp_desc">Celsius, Fahrenheit</string>
|
||||
|
||||
<string name="unit_wind">Wind Speed Unit</string>
|
||||
<string name="kmh">Kilometers per Hour (km/h)</string>
|
||||
<string name="ms">Meters per second (m/s)</string>
|
||||
<string name="mph">Miles per hour (mph)</string>
|
||||
<string name="kn">Knots (kn)</string>
|
||||
<string name="unit_wind_desc">Km/h, m/s, Mph, Knots</string>
|
||||
|
||||
<string name="unit_precipitation">Precipitation Unit</string>
|
||||
<string name="mm">Millimeters (mm)</string>
|
||||
<string name="inch">Inches (in)</string>
|
||||
<string name="unit_precipitation_desc">Millimeters, Inches</string>
|
||||
|
||||
<string name="weather_high">High: %1$d%2$s</string>
|
||||
<string name="weather_low">Low: %1$d%2$s</string>
|
||||
<string name="feels_like">Feels like %1$d%2$s</string>
|
||||
<string name="weather_precipitation">Precipitation: %1$d﹪</string>
|
||||
<string name="weather_description">%1$s - Feels like %2$d%3$s</string>
|
||||
|
||||
<string name="precipitation_probability">Precipitation Probability</string>
|
||||
<string name="precipitation_amount">Precipitation Amount</string>
|
||||
<string name="wind_speed">Wind Speed</string>
|
||||
|
||||
<string name="today_in">Today in %1$s</string>
|
||||
|
||||
|
|
Reference in a new issue