not working, nullpointerexception
This commit is contained in:
parent
812e627abb
commit
78285ab5c2
24 changed files with 353 additions and 118 deletions
|
@ -1,9 +1,10 @@
|
||||||
package com.henryhiles.qweather.di
|
package com.henryhiles.qweather.di
|
||||||
|
|
||||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.AppearanceSettingsScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.PreferenceManager
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel
|
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.factoryOf
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
@ -35,8 +36,9 @@ val appModule = module {
|
||||||
}
|
}
|
||||||
|
|
||||||
singleOf(::provideWeatherApi)
|
singleOf(::provideWeatherApi)
|
||||||
singleOf(::PreferenceManager)
|
singleOf(::AppearancePreferenceManager)
|
||||||
factoryOf(::AppearanceSettingsScreenModel)
|
|
||||||
|
|
||||||
factoryOf(::WeatherScreenModel)
|
factoryOf(::AppearancePreferencesScreenModel)
|
||||||
|
factoryOf(::HourlyWeatherScreenModel)
|
||||||
|
factoryOf(::DailyWeatherScreenModel)
|
||||||
}
|
}
|
|
@ -1,35 +1,56 @@
|
||||||
package com.henryhiles.qweather.domain.mappers
|
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.remote.WeatherDto
|
||||||
import com.henryhiles.qweather.domain.weather.WeatherData
|
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||||
import com.henryhiles.qweather.domain.weather.WeatherInfo
|
import com.henryhiles.qweather.domain.weather.HourlyWeatherData
|
||||||
|
import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo
|
||||||
import com.henryhiles.qweather.domain.weather.WeatherType
|
import com.henryhiles.qweather.domain.weather.WeatherType
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
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<Int, List<WeatherData>> {
|
fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): Map<Int, List<HourlyWeatherData>> {
|
||||||
return time.mapIndexed { index, time ->
|
return times.mapIndexed { index, time ->
|
||||||
IndexedWeatherData(
|
IndexedHourlyWeatherData(
|
||||||
index = index, data = WeatherData(
|
index = index,
|
||||||
|
data = HourlyWeatherData(
|
||||||
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
||||||
temperatureCelsius = temperatures[index].toInt(),
|
temperature = temperatures[index].roundToInt(),
|
||||||
pressure = pressures[index],
|
apparentTemperature = apparentTemperatures[index].roundToInt(),
|
||||||
windSpeed = windSpeeds[index],
|
windSpeed = windSpeeds[index].roundToInt(),
|
||||||
humidity = humidities[index],
|
precipitationProbability = precipitationProbabilities[index],
|
||||||
weatherType = WeatherType.fromWMO(weatherCodes[index])
|
weatherType = WeatherType.fromWMO(weatherCodes[index])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } }
|
}.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun WeatherDto.toWeatherInfo(): WeatherInfo {
|
fun DailyWeatherDataDto.toDailyWeatherDataMap(): List<DailyWeatherData> {
|
||||||
val weatherDataMap = weatherData.toWeatherDataMap()
|
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 now = LocalDateTime.now()
|
||||||
val currentWeatherData = weatherDataMap[0]?.find {
|
val currentWeatherData = weatherDataMap[0]?.find {
|
||||||
it.time.hour == now.hour
|
it.time.hour == now.hour
|
||||||
}
|
}
|
||||||
return WeatherInfo(weatherDataPerDay = weatherDataMap, currentWeatherData = currentWeatherData)
|
return HourlyWeatherInfo(
|
||||||
|
weatherDataPerDay = weatherDataMap,
|
||||||
|
currentWeatherData = currentWeatherData
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -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<String>,
|
||||||
|
@field:Json(name = "weathercode")
|
||||||
|
val weatherCodes: List<Int>,
|
||||||
|
@field:Json(name = "temperature_2m_max")
|
||||||
|
val temperaturesMax: List<Double>,
|
||||||
|
@field:Json(name = "temperature_2m_min")
|
||||||
|
val temperaturesMin: List<Double>,
|
||||||
|
@field:Json(name = "apparent_temperature_max")
|
||||||
|
val apparentTemperaturesMax: List<Double>,
|
||||||
|
@field:Json(name = "apparent_temperature_min")
|
||||||
|
val apparentTemperaturesMin: List<Double>
|
||||||
|
)
|
|
@ -2,16 +2,17 @@ package com.henryhiles.qweather.domain.remote
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
data class WeatherDataDto(
|
data class HourlyWeatherDataDto(
|
||||||
val time: List<String>,
|
@field:Json(name = "time")
|
||||||
|
val times: List<String>,
|
||||||
@field:Json(name = "temperature_2m")
|
@field:Json(name = "temperature_2m")
|
||||||
val temperatures: List<Double>,
|
val temperatures: List<Double>,
|
||||||
|
@field:Json(name = "apparent_temperature")
|
||||||
|
val apparentTemperatures: List<Double>,
|
||||||
@field:Json(name = "weathercode")
|
@field:Json(name = "weathercode")
|
||||||
val weatherCodes: List<Int>,
|
val weatherCodes: List<Int>,
|
||||||
@field:Json(name = "pressure_msl")
|
@field:Json(name = "precipitation_probability")
|
||||||
val pressures: List<Double>,
|
val precipitationProbabilities: List<Int>,
|
||||||
@field:Json(name = "windspeed_10m")
|
@field:Json(name = "windspeed_10m")
|
||||||
val windSpeeds: List<Double>,
|
val windSpeeds: List<Double>,
|
||||||
@field:Json(name = "relativehumidity_2m")
|
|
||||||
val humidities: List<Double>
|
|
||||||
)
|
)
|
|
@ -3,10 +3,17 @@ package com.henryhiles.qweather.domain.remote
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Query
|
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 {
|
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(
|
suspend fun getWeatherData(
|
||||||
@Query("latitude") lat: Double,
|
@Query("latitude") lat: Double,
|
||||||
@Query("longitude") long: Double
|
@Query("longitude") long: Double,
|
||||||
): WeatherDto
|
): WeatherDto
|
||||||
}
|
}
|
|
@ -4,5 +4,8 @@ import com.squareup.moshi.Json
|
||||||
|
|
||||||
data class WeatherDto(
|
data class WeatherDto(
|
||||||
@field:Json(name = "hourly")
|
@field:Json(name = "hourly")
|
||||||
val weatherData: WeatherDataDto
|
val hourlyWeatherData: HourlyWeatherDataDto,
|
||||||
|
|
||||||
|
@field:Json(name = "daily")
|
||||||
|
val dailyWeatherData: DailyWeatherDataDto
|
||||||
)
|
)
|
|
@ -1,14 +1,35 @@
|
||||||
package com.henryhiles.qweather.domain.repository
|
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.remote.WeatherApi
|
||||||
import com.henryhiles.qweather.domain.util.Resource
|
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) {
|
class WeatherRepository constructor(private val api: WeatherApi) {
|
||||||
suspend fun getWeatherData(lat: Double, long: Double): Resource<WeatherInfo> {
|
suspend fun getHourlyWeatherData(lat: Double, long: Double): Resource<HourlyWeatherInfo> {
|
||||||
return try {
|
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<List<DailyWeatherData>> {
|
||||||
|
return try {
|
||||||
|
Resource.Success(
|
||||||
|
data = api.getWeatherData(
|
||||||
|
lat = lat,
|
||||||
|
long = long
|
||||||
|
).dailyWeatherData.toDailyWeatherDataMap()
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
Resource.Error(e.message ?: "An unknown error occurred.")
|
Resource.Error(e.message ?: "An unknown error occurred.")
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -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,
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.henryhiles.qweather.domain.weather
|
||||||
|
|
||||||
|
data class HourlyWeatherInfo(
|
||||||
|
val weatherDataPerDay: Map<Int, List<HourlyWeatherData>>,
|
||||||
|
val currentWeatherData: HourlyWeatherData?
|
||||||
|
)
|
|
@ -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
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.henryhiles.qweather.domain.weather
|
|
||||||
|
|
||||||
data class WeatherInfo(
|
|
||||||
val weatherDataPerDay: Map<Int, List<WeatherData>>,
|
|
||||||
val currentWeatherData: WeatherData?
|
|
||||||
)
|
|
|
@ -9,13 +9,13 @@ import androidx.compose.material3.Surface
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.SlideTransition
|
import cafe.adriel.voyager.transitions.SlideTransition
|
||||||
import com.henryhiles.qweather.presentation.screen.MainScreen
|
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.screenmodel.Theme
|
||||||
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
class QWeatherActivity : ComponentActivity() {
|
class QWeatherActivity : ComponentActivity() {
|
||||||
private val prefs: PreferenceManager by inject()
|
private val prefs: AppearancePreferenceManager by inject()
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.henryhiles.qweather.presentation.components.weather
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.Card
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.henryhiles.qweather.R
|
import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.presentation.components.WeatherDataDisplay
|
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.WeatherState
|
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) {
|
fun WeatherCard(state: HourlyWeatherState, modifier: Modifier = Modifier) {
|
||||||
state.weatherInfo?.currentWeatherData?.let {
|
state.hourlyWeatherInfo?.currentWeatherData?.let {
|
||||||
val formattedTime = remember(it) {
|
val formattedTime = remember(it) {
|
||||||
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) {
|
||||||
modifier = Modifier.width(200.dp)
|
modifier = Modifier.width(200.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.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))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(text = it.weatherType.weatherDesc, fontSize = 20.sp)
|
Text(text = it.weatherType.weatherDesc, fontSize = 20.sp)
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
@ -57,19 +57,19 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) {
|
||||||
horizontalArrangement = Arrangement.SpaceAround
|
horizontalArrangement = Arrangement.SpaceAround
|
||||||
) {
|
) {
|
||||||
WeatherDataDisplay(
|
WeatherDataDisplay(
|
||||||
value = it.pressure.roundToInt(),
|
value = it.apparentTemperature,
|
||||||
unit = "hpa",
|
unit = "°C",
|
||||||
icon = ImageVector.vectorResource(id = R.drawable.ic_pressure),
|
icon = Icons.Default.Thermostat,
|
||||||
description = "Pressure",
|
description = "Feels like",
|
||||||
)
|
)
|
||||||
WeatherDataDisplay(
|
WeatherDataDisplay(
|
||||||
value = it.humidity.roundToInt(),
|
value = it.precipitationProbability,
|
||||||
unit = "%",
|
unit = "%",
|
||||||
icon = ImageVector.vectorResource(id = R.drawable.ic_drop),
|
icon = ImageVector.vectorResource(id = R.drawable.ic_drop),
|
||||||
description = "Humidity"
|
description = "Chance of precipitation"
|
||||||
)
|
)
|
||||||
WeatherDataDisplay(
|
WeatherDataDisplay(
|
||||||
value = it.windSpeed.roundToInt(),
|
value = it.windSpeed,
|
||||||
unit = "km/h",
|
unit = "km/h",
|
||||||
icon = ImageVector.vectorResource(id = R.drawable.ic_wind),
|
icon = ImageVector.vectorResource(id = R.drawable.ic_wind),
|
||||||
description = "Wind Speed",
|
description = "Wind Speed",
|
||||||
|
|
|
@ -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.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
@ -10,11 +10,13 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WeatherDataDisplay(
|
fun WeatherDataDisplay(
|
||||||
value: Int,
|
value: Int?,
|
||||||
unit: String,
|
unit: String,
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
description: String,
|
description: String,
|
||||||
|
@ -24,10 +26,9 @@ fun WeatherDataDisplay(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = description,
|
contentDescription = description,
|
||||||
// tint = MaterialTheme.colorScheme.onSecondary,
|
|
||||||
modifier = Modifier.size(25.dp)
|
modifier = Modifier.size(25.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(text = "$value$unit")
|
Text(text = if (value == null) stringResource(id = R.string.unknown) else "$value$unit")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
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.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.henryhiles.qweather.presentation.components.weather.WeatherHour
|
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.WeatherState
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WeatherForecast(state: WeatherState, modifier: Modifier = Modifier) {
|
fun WeatherForecast(state: HourlyWeatherState, modifier: Modifier = Modifier) {
|
||||||
state.weatherInfo?.weatherDataPerDay?.get(0)?.let {
|
state.hourlyWeatherInfo?.weatherDataPerDay?.get(0)?.let {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -25,7 +24,7 @@ fun WeatherForecast(state: WeatherState, modifier: Modifier = Modifier) {
|
||||||
Text(text = "Today", fontSize = 20.sp, color = Color.White)
|
Text(text = "Today", fontSize = 20.sp, color = Color.White)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
val rowState = rememberLazyListState(LocalDateTime.now().hour)
|
val rowState = rememberLazyListState(LocalDateTime.now().hour)
|
||||||
|
|
||||||
LazyRow(state = rowState) {
|
LazyRow(state = rowState) {
|
||||||
items(it) {
|
items(it) {
|
||||||
WeatherHour(
|
WeatherHour(
|
||||||
|
|
|
@ -11,11 +11,11 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
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
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WeatherHour(data: WeatherData, modifier: Modifier = Modifier) {
|
fun WeatherHour(data: HourlyWeatherData, modifier: Modifier = Modifier) {
|
||||||
data.let {
|
data.let {
|
||||||
val formattedTime = remember(it) {
|
val formattedTime = remember(it) {
|
||||||
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
|
@ -29,10 +29,10 @@ fun WeatherHour(data: WeatherData, modifier: Modifier = Modifier) {
|
||||||
Text(text = formattedTime)
|
Text(text = formattedTime)
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = it.weatherType.iconRes),
|
painter = painterResource(id = it.weatherType.iconRes),
|
||||||
contentDescription = "Image of ${data.weatherType.weatherDesc}",
|
contentDescription = "Image of ${it.weatherType.weatherDesc}",
|
||||||
modifier = Modifier.width(40.dp)
|
modifier = Modifier.width(40.dp)
|
||||||
)
|
)
|
||||||
Text(text = "${it.temperatureCelsius}°C")
|
Text(text = "${it.temperature}°C")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.presentation.components.LargeToolbar
|
import com.henryhiles.qweather.presentation.components.LargeToolbar
|
||||||
import com.henryhiles.qweather.presentation.components.settings.SettingsItemChoice
|
import com.henryhiles.qweather.presentation.components.settings.SettingsItemChoice
|
||||||
import com.henryhiles.qweather.presentation.components.settings.SettingsSwitch
|
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 {
|
class AppearanceSettingsScreen : Screen {
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class AppearanceSettingsScreen : Screen {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Screen(
|
private fun Screen(
|
||||||
screenModel: AppearanceSettingsScreenModel = getScreenModel()
|
screenModel: AppearancePreferencesScreenModel = getScreenModel()
|
||||||
) {
|
) {
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import com.henryhiles.qweather.R
|
import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.domain.manager.BasePreferenceManager
|
import com.henryhiles.qweather.domain.manager.BasePreferenceManager
|
||||||
|
|
||||||
class AppearanceSettingsManager(context: Context) :
|
class AppearancePreferenceManager(context: Context) :
|
||||||
BasePreferenceManager(context.getSharedPreferences("prefs", Context.MODE_PRIVATE)) {
|
BasePreferenceManager(context.getSharedPreferences("prefs", Context.MODE_PRIVATE)) {
|
||||||
var theme by enumPreference("theme", Theme.SYSTEM)
|
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||||
var monet by booleanPreference("monet", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
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);
|
DARK(R.string.theme_dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppearanceSettingsScreenModel(
|
class AppearancePreferencesScreenModel(
|
||||||
val prefs: AppearanceSettingsManager
|
val prefs: AppearancePreferenceManager
|
||||||
) : ScreenModel
|
) : ScreenModel
|
|
@ -8,20 +8,20 @@ import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import com.henryhiles.qweather.domain.location.LocationTracker
|
import com.henryhiles.qweather.domain.location.LocationTracker
|
||||||
import com.henryhiles.qweather.domain.repository.WeatherRepository
|
import com.henryhiles.qweather.domain.repository.WeatherRepository
|
||||||
import com.henryhiles.qweather.domain.util.Resource
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
data class WeatherState(
|
data class DailyWeatherState(
|
||||||
val weatherInfo: WeatherInfo? = null,
|
val dailyWeatherData: List<DailyWeatherData>? = null,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val error: String? = null
|
val error: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class WeatherScreenModel constructor(
|
class DailyWeatherScreenModel constructor(
|
||||||
private val repository: WeatherRepository,
|
private val repository: WeatherRepository,
|
||||||
private val locationTracker: LocationTracker,
|
private val locationTracker: LocationTracker,
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
var state by mutableStateOf(WeatherState())
|
var state by mutableStateOf(DailyWeatherState())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun loadWeatherInfo() {
|
fun loadWeatherInfo() {
|
||||||
|
@ -30,10 +30,10 @@ class WeatherScreenModel constructor(
|
||||||
val currentLocation = locationTracker.getCurrentLocation()
|
val currentLocation = locationTracker.getCurrentLocation()
|
||||||
currentLocation?.let { location ->
|
currentLocation?.let { location ->
|
||||||
state = when (val result =
|
state = when (val result =
|
||||||
repository.getWeatherData(location.latitude, location.longitude)) {
|
repository.getDailyWeatherData(location.latitude, location.longitude)) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
state.copy(
|
state.copy(
|
||||||
weatherInfo = result.data,
|
dailyWeatherData = result.data,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = null
|
error = null
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,7 @@ class WeatherScreenModel constructor(
|
||||||
|
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
state.copy(
|
state.copy(
|
||||||
weatherInfo = null,
|
dailyWeatherData = null,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = result.message
|
error = result.message
|
||||||
)
|
)
|
|
@ -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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,10 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -21,8 +24,8 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.henryhiles.qweather.R
|
import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.presentation.components.weather.WeatherCard
|
import com.henryhiles.qweather.presentation.components.weather.WeatherCard
|
||||||
import com.henryhiles.qweather.presentation.components.WeatherForecast
|
import com.henryhiles.qweather.presentation.components.weather.WeatherForecast
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel
|
||||||
|
|
||||||
object TodayTab : Tab {
|
object TodayTab : Tab {
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
|
@ -43,7 +46,7 @@ object TodayTab : Tab {
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val weatherViewModel = getScreenModel<WeatherScreenModel>()
|
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
|
||||||
|
|
||||||
val permissionsState = rememberPermissionState(
|
val permissionsState = rememberPermissionState(
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
@ -65,27 +68,16 @@ object TodayTab : Tab {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
weatherViewModel.state.error != null -> {
|
weatherViewModel.state.error != null -> {
|
||||||
AlertDialog(onDismissRequest = {}) {
|
AlertDialog(
|
||||||
Surface(
|
onDismissRequest = {},
|
||||||
shape = MaterialTheme.shapes.large
|
confirmButton = {},
|
||||||
) {
|
title = { Text(text = "An error occurred") }, text = {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
text = "An error occurred",
|
text = weatherViewModel.state.error!!,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
|
||||||
text = weatherViewModel.state.error!!,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
package com.henryhiles.qweather.presentation.tabs
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.DateRange
|
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.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
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.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.Tab
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
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.R
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel
|
||||||
|
|
||||||
object WeekTab : Tab {
|
object WeekTab : Tab {
|
||||||
override val options: TabOptions
|
override val options: TabOptions
|
||||||
|
@ -27,8 +42,91 @@ object WeekTab : Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
Text(text = "Week Screen")
|
val weatherViewModel = getScreenModel<DailyWeatherScreenModel>()
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,4 +17,6 @@
|
||||||
<string name="theme_system">System</string>
|
<string name="theme_system">System</string>
|
||||||
<string name="theme_light">Light</string>
|
<string name="theme_light">Light</string>
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
|
|
||||||
|
<string name="unknown">Unknown</string>
|
||||||
</resources>
|
</resources>
|
Reference in a new issue