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
|
||||
|
||||
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)
|
||||
}
|
|
@ -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<Int, List<WeatherData>> {
|
||||
return time.mapIndexed { index, time ->
|
||||
IndexedWeatherData(
|
||||
index = index, data = WeatherData(
|
||||
fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): Map<Int, List<HourlyWeatherData>> {
|
||||
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<DailyWeatherData> {
|
||||
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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
||||
data class WeatherDataDto(
|
||||
val time: List<String>,
|
||||
data class HourlyWeatherDataDto(
|
||||
@field:Json(name = "time")
|
||||
val times: List<String>,
|
||||
@field:Json(name = "temperature_2m")
|
||||
val temperatures: List<Double>,
|
||||
@field:Json(name = "apparent_temperature")
|
||||
val apparentTemperatures: List<Double>,
|
||||
@field:Json(name = "weathercode")
|
||||
val weatherCodes: List<Int>,
|
||||
@field:Json(name = "pressure_msl")
|
||||
val pressures: List<Double>,
|
||||
@field:Json(name = "precipitation_probability")
|
||||
val precipitationProbabilities: List<Int>,
|
||||
@field:Json(name = "windspeed_10m")
|
||||
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.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
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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<WeatherInfo> {
|
||||
suspend fun getHourlyWeatherData(lat: Double, long: Double): Resource<HourlyWeatherInfo> {
|
||||
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) {
|
||||
e.printStackTrace()
|
||||
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.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?) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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<DailyWeatherData>? = 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
|
||||
)
|
|
@ -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.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<WeatherScreenModel>()
|
||||
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
|
||||
|
||||
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(
|
||||
|
|
|
@ -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<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_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
|
||||
<string name="unknown">Unknown</string>
|
||||
</resources>
|
Reference in a new issue