many changes including expandable days, also bump dependencies
This commit is contained in:
parent
0b3254877b
commit
dc1d3328cb
14 changed files with 150 additions and 95 deletions
|
@ -10,7 +10,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.henryhiles.qweather"
|
applicationId = "com.henryhiles.qweather"
|
||||||
minSdk = 21
|
minSdk = 30
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
@ -57,19 +57,28 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val composeVersion = "1.4.0"
|
implementation("androidx.core:core-ktx:1.10.0")
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.compose.material3:material3:1.1.0-beta02")
|
||||||
implementation("androidx.compose.ui:ui:$composeVersion")
|
|
||||||
implementation("androidx.compose.material3:material3:1.1.0-beta01")
|
|
||||||
implementation("androidx.compose.material:material-icons-extended:$composeVersion")
|
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
|
||||||
implementation("androidx.activity:activity-compose:1.7.0")
|
implementation("androidx.activity:activity-compose:1.7.0")
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.10.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
val lifecycleVersion = "2.6.1"
|
||||||
|
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion")
|
||||||
|
|
||||||
|
// Compose
|
||||||
|
val composeVersion = "1.4.0"
|
||||||
|
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
|
||||||
|
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||||
|
implementation("androidx.compose.material:material-icons-extended:$composeVersion")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
|
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")
|
|
||||||
|
|
||||||
// Voyager
|
// Voyager
|
||||||
val voyagerVersion = "1.0.0-rc04"
|
val voyagerVersion = "1.0.0-rc04"
|
||||||
|
@ -88,9 +97,9 @@ dependencies {
|
||||||
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
||||||
|
|
||||||
// Retrofit
|
// Retrofit
|
||||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
val retrofitVersion = "2.9.0"
|
||||||
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
|
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
implementation("com.squareup.retrofit2:converter-moshi:$retrofitVersion")
|
||||||
|
|
||||||
// Accompanist
|
// Accompanist
|
||||||
val accompanistVersion = "0.30.0"
|
val accompanistVersion = "0.30.0"
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.henryhiles.qweather.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
|
||||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
@ -17,7 +16,7 @@ import retrofit2.create
|
||||||
private fun isNetworkAvailable(context: Context): Boolean {
|
private fun isNetworkAvailable(context: Context): Boolean {
|
||||||
val connectivityManager =
|
val connectivityManager =
|
||||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
val networkCapabilities = connectivityManager.activeNetwork ?: return false
|
val networkCapabilities = connectivityManager.activeNetwork ?: return false
|
||||||
val activeNetwork =
|
val activeNetwork =
|
||||||
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
|
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
|
||||||
|
@ -27,10 +26,6 @@ private fun isNetworkAvailable(context: Context): Boolean {
|
||||||
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val activeNetworkInfo = connectivityManager.activeNetworkInfo ?: return false
|
|
||||||
return activeNetworkInfo.isConnected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val appModule = module {
|
val appModule = module {
|
||||||
|
|
|
@ -25,26 +25,5 @@ class LocationTracker constructor(
|
||||||
if (!hasAccessFineLocationPermission || !isGpsEnabled) return null
|
if (!hasAccessFineLocationPermission || !isGpsEnabled) return null
|
||||||
|
|
||||||
return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
// return suspendCancellableCoroutine { cont ->
|
|
||||||
// locationManager.getLastKnownLocation.apply {
|
|
||||||
// if (isComplete) {
|
|
||||||
// if (isSuccessful) {
|
|
||||||
// cont.resume(result)
|
|
||||||
// } else {
|
|
||||||
// cont.resume(null)
|
|
||||||
// }
|
|
||||||
// return@suspendCancellableCoroutine
|
|
||||||
// }
|
|
||||||
// addOnSuccessListener {
|
|
||||||
// cont.resume(it)
|
|
||||||
// }
|
|
||||||
// addOnFailureListener {
|
|
||||||
// cont.resume(null)
|
|
||||||
// }
|
|
||||||
// addOnCanceledListener {
|
|
||||||
// cont.cancel()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,30 +15,32 @@ import kotlin.math.roundToInt
|
||||||
private data class IndexedHourlyWeatherData(val index: Int, val data: HourlyWeatherData)
|
private data class IndexedHourlyWeatherData(val index: Int, val data: HourlyWeatherData)
|
||||||
|
|
||||||
fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): Map<Int, List<HourlyWeatherData>> {
|
fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): Map<Int, List<HourlyWeatherData>> {
|
||||||
return times.mapIndexed { index, time ->
|
return time.mapIndexed { index, time ->
|
||||||
IndexedHourlyWeatherData(
|
IndexedHourlyWeatherData(
|
||||||
index = index,
|
index = index,
|
||||||
data = HourlyWeatherData(
|
data = HourlyWeatherData(
|
||||||
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
||||||
temperature = temperatures[index].roundToInt(),
|
temperature = temperature[index].roundToInt(),
|
||||||
apparentTemperature = apparentTemperatures[index].roundToInt(),
|
apparentTemperature = apparentTemperature[index].roundToInt(),
|
||||||
windSpeed = windSpeeds[index].roundToInt(),
|
windSpeed = windSpeed[index].roundToInt(),
|
||||||
precipitationProbability = precipitationProbabilities.getOrNull(index),
|
precipitationProbability = precipitationProbability.getOrNull(index),
|
||||||
weatherType = WeatherType.fromWMO(weatherCodes[index])
|
weatherType = WeatherType.fromWMO(weatherCode[index])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } }
|
}.groupBy { it.index / 24 }.mapValues { entry -> entry.value.map { it.data } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DailyWeatherDataDto.toDailyWeatherDataMap(): List<DailyWeatherData> {
|
fun DailyWeatherDataDto.toDailyWeatherDataMap(): List<DailyWeatherData> {
|
||||||
return dates.mapIndexed { index, date ->
|
return date.mapIndexed { index, date ->
|
||||||
DailyWeatherData(
|
DailyWeatherData(
|
||||||
date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
|
date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
|
||||||
weatherType = WeatherType.fromWMO(weatherCodes[index]),
|
weatherType = WeatherType.fromWMO(weatherCode[index]),
|
||||||
apparentTemperatureMax = apparentTemperaturesMax[index].roundToInt(),
|
apparentTemperatureMax = apparentTemperatureMax[index].roundToInt(),
|
||||||
apparentTemperatureMin = apparentTemperaturesMin[index].roundToInt(),
|
apparentTemperatureMin = apparentTemperatureMin[index].roundToInt(),
|
||||||
temperatureMax = temperaturesMax[index].roundToInt(),
|
temperatureMax = temperatureMax[index].roundToInt(),
|
||||||
temperatureMin = temperaturesMin[index].roundToInt()
|
temperatureMin = temperatureMin[index].roundToInt(),
|
||||||
|
precipitationProbabilityMax = precipitationProbabilityMax.getOrNull(index),
|
||||||
|
windSpeedMax = windSpeedMax[index].roundToInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,21 @@ import com.squareup.moshi.Json
|
||||||
|
|
||||||
data class DailyWeatherDataDto(
|
data class DailyWeatherDataDto(
|
||||||
@field:Json(name = "time")
|
@field:Json(name = "time")
|
||||||
val dates: List<String>,
|
val date: List<String>,
|
||||||
@field:Json(name = "weathercode")
|
@field:Json(name = "weathercode")
|
||||||
val weatherCodes: List<Int>,
|
val weatherCode: List<Int>,
|
||||||
|
@field:Json(name = "precipitation_probability_max")
|
||||||
|
val precipitationProbabilityMax: List<Int>,
|
||||||
|
@field:Json(name = "precipitation_sum")
|
||||||
|
val precipitationSum: List<Double>,
|
||||||
|
@field:Json(name = "windspeed_10m_max")
|
||||||
|
val windSpeedMax: List<Double>,
|
||||||
@field:Json(name = "temperature_2m_max")
|
@field:Json(name = "temperature_2m_max")
|
||||||
val temperaturesMax: List<Double>,
|
val temperatureMax: List<Double>,
|
||||||
@field:Json(name = "temperature_2m_min")
|
@field:Json(name = "temperature_2m_min")
|
||||||
val temperaturesMin: List<Double>,
|
val temperatureMin: List<Double>,
|
||||||
@field:Json(name = "apparent_temperature_max")
|
@field:Json(name = "apparent_temperature_max")
|
||||||
val apparentTemperaturesMax: List<Double>,
|
val apparentTemperatureMax: List<Double>,
|
||||||
@field:Json(name = "apparent_temperature_min")
|
@field:Json(name = "apparent_temperature_min")
|
||||||
val apparentTemperaturesMin: List<Double>
|
val apparentTemperatureMin: List<Double>
|
||||||
)
|
)
|
|
@ -4,15 +4,15 @@ import com.squareup.moshi.Json
|
||||||
|
|
||||||
data class HourlyWeatherDataDto(
|
data class HourlyWeatherDataDto(
|
||||||
@field:Json(name = "time")
|
@field:Json(name = "time")
|
||||||
val times: List<String>,
|
val time: List<String>,
|
||||||
@field:Json(name = "temperature_2m")
|
@field:Json(name = "temperature_2m")
|
||||||
val temperatures: List<Double>,
|
val temperature: List<Double>,
|
||||||
@field:Json(name = "apparent_temperature")
|
@field:Json(name = "apparent_temperature")
|
||||||
val apparentTemperatures: List<Double>,
|
val apparentTemperature: List<Double>,
|
||||||
@field:Json(name = "weathercode")
|
@field:Json(name = "weathercode")
|
||||||
val weatherCodes: List<Int>,
|
val weatherCode: List<Int>,
|
||||||
@field:Json(name = "precipitation_probability")
|
@field:Json(name = "precipitation_probability")
|
||||||
val precipitationProbabilities: List<Int>,
|
val precipitationProbability: List<Int>,
|
||||||
@field:Json(name = "windspeed_10m")
|
@field:Json(name = "windspeed_10m")
|
||||||
val windSpeeds: List<Double>,
|
val windSpeed: List<Double>,
|
||||||
)
|
)
|
|
@ -5,7 +5,7 @@ import retrofit2.http.Headers
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
const val DAILY =
|
const val DAILY =
|
||||||
"daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min"
|
"daily=weathercode,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,precipitation_sum,precipitation_probability_max,windspeed_10m_max"
|
||||||
const val HOURLY =
|
const val HOURLY =
|
||||||
"hourly=temperature_2m,apparent_temperature,precipitation_probability,weathercode,windspeed_10m"
|
"hourly=temperature_2m,apparent_temperature,precipitation_probability,weathercode,windspeed_10m"
|
||||||
const val TIMEZONE = "timezone=auto"
|
const val TIMEZONE = "timezone=auto"
|
||||||
|
|
|
@ -9,4 +9,6 @@ data class DailyWeatherData(
|
||||||
val temperatureMin: Int,
|
val temperatureMin: Int,
|
||||||
val apparentTemperatureMax: Int,
|
val apparentTemperatureMax: Int,
|
||||||
val apparentTemperatureMin: Int,
|
val apparentTemperatureMin: Int,
|
||||||
|
val precipitationProbabilityMax: Int?,
|
||||||
|
val windSpeedMax: Int
|
||||||
)
|
)
|
|
@ -1,7 +1,11 @@
|
||||||
package com.henryhiles.qweather.presentation.components.weather
|
package com.henryhiles.qweather.presentation.components.weather
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Water
|
||||||
|
import androidx.compose.material.icons.outlined.WaterDrop
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -11,13 +15,16 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
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.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
fun WeatherDay(dailyWeatherData: DailyWeatherData, expanded: Boolean, onExpand: () -> Unit) {
|
||||||
val formattedDate by remember {
|
val formattedDate by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
dailyWeatherData.date.format(
|
dailyWeatherData.date.format(
|
||||||
|
@ -27,10 +34,12 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
horizontal = 16.dp,
|
horizontal = 16.dp,
|
||||||
vertical = 8.dp
|
vertical = 8.dp
|
||||||
)
|
)
|
||||||
|
.clickable(onClick = onExpand)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -40,9 +49,10 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = dailyWeatherData.weatherType.iconRes),
|
painter = painterResource(id = dailyWeatherData.weatherType.iconRes),
|
||||||
contentDescription = "Image of ${dailyWeatherData.weatherType}",
|
contentDescription = "Image of ${dailyWeatherData.weatherType.weatherDesc}",
|
||||||
modifier = Modifier.width(48.dp)
|
modifier = Modifier.width(48.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
|
@ -57,5 +67,36 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData) {
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (expanded) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp, 0.dp, 16.dp, 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
WeatherDataDisplay(
|
||||||
|
value = dailyWeatherData.precipitationProbabilityMax,
|
||||||
|
unit = "%",
|
||||||
|
icon = Icons.Outlined.WaterDrop,
|
||||||
|
description = "Chance of rain"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
WeatherDataDisplay(
|
||||||
|
value = dailyWeatherData.windSpeedMax,
|
||||||
|
unit = "mm",
|
||||||
|
icon = Icons.Outlined.Water,
|
||||||
|
description = "Precipitation Amount"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
WeatherDataDisplay(
|
||||||
|
value = dailyWeatherData.windSpeedMax,
|
||||||
|
unit = "km/h",
|
||||||
|
icon = ImageVector.vectorResource(id = R.drawable.ic_wind),
|
||||||
|
description = "Wind Speed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,8 @@ import kotlinx.coroutines.launch
|
||||||
data class DailyWeatherState(
|
data class DailyWeatherState(
|
||||||
val dailyWeatherData: List<DailyWeatherData>? = null,
|
val dailyWeatherData: List<DailyWeatherData>? = null,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val error: String? = null
|
val error: String? = null,
|
||||||
|
val expanded: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class DailyWeatherScreenModel constructor(
|
class DailyWeatherScreenModel constructor(
|
||||||
|
@ -60,4 +61,8 @@ class DailyWeatherScreenModel constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setExpanded(index: Int?) {
|
||||||
|
state = state.copy(expanded = index)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package com.henryhiles.qweather.presentation.screenmodel
|
package com.henryhiles.qweather.presentation.screenmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
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
|
||||||
|
@ -14,12 +16,14 @@ import kotlinx.coroutines.launch
|
||||||
data class HourlyWeatherState(
|
data class HourlyWeatherState(
|
||||||
val hourlyWeatherInfo: HourlyWeatherInfo? = null,
|
val hourlyWeatherInfo: HourlyWeatherInfo? = null,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val error: String? = null
|
val error: String? = null,
|
||||||
|
val expanded: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class HourlyWeatherScreenModel constructor(
|
class HourlyWeatherScreenModel constructor(
|
||||||
private val repository: WeatherRepository,
|
private val repository: WeatherRepository,
|
||||||
private val locationTracker: LocationTracker,
|
private val locationTracker: LocationTracker,
|
||||||
|
private val context: Context
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
var state by mutableStateOf(HourlyWeatherState())
|
var state by mutableStateOf(HourlyWeatherState())
|
||||||
private set
|
private set
|
||||||
|
@ -54,7 +58,7 @@ class HourlyWeatherScreenModel constructor(
|
||||||
} ?: kotlin.run {
|
} ?: kotlin.run {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = "Couldn't retrieve location. Make sure to grant permission and enable GPS."
|
error = context.getString(R.string.error_location)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ object TodayTab : NavigationTab {
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
|
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
|
||||||
|
|
||||||
val permissionsState = rememberPermissionState(
|
val permissionsState = rememberPermissionState(
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
) {
|
) {
|
||||||
|
@ -68,14 +67,20 @@ object TodayTab : NavigationTab {
|
||||||
weatherViewModel.state.error != null -> {
|
weatherViewModel.state.error != null -> {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
confirmButton = {},
|
confirmButton = {
|
||||||
title = { Text(text = "An error occurred") }, text = {
|
TextButton(onClick = { weatherViewModel.loadWeatherInfo() }) {
|
||||||
|
Text(text = stringResource(id = R.string.action_try_again))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text(text = stringResource(id = R.string.error)) },
|
||||||
|
text = {
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
text = weatherViewModel.state.error!!,
|
text = weatherViewModel.state.error!!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
@ -3,15 +3,13 @@ package com.henryhiles.qweather.presentation.tabs
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
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.DateRange
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
@ -84,8 +82,13 @@ object WeekTab : NavigationTab {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
weatherViewModel.state.dailyWeatherData?.let { data ->
|
weatherViewModel.state.dailyWeatherData?.let { data ->
|
||||||
items(data) {
|
itemsIndexed(data) { index, dailyData ->
|
||||||
WeatherDay(dailyWeatherData = it)
|
val expanded = weatherViewModel.state.expanded == index
|
||||||
|
WeatherDay(
|
||||||
|
dailyWeatherData = dailyData,
|
||||||
|
expanded = expanded,
|
||||||
|
onExpand = { weatherViewModel.setExpanded(if (expanded) null else index) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<string name="action_confirm">Confirm</string>
|
<string name="action_confirm">Confirm</string>
|
||||||
<string name="action_open_about">About</string>
|
<string name="action_open_about">About</string>
|
||||||
<string name="action_reload">Reload</string>
|
<string name="action_reload">Reload</string>
|
||||||
|
<string name="action_try_again">Try Again</string>
|
||||||
|
|
||||||
<string name="appearance_theme">Theme</string>
|
<string name="appearance_theme">Theme</string>
|
||||||
<string name="appearance_monet">Dynamic Theme</string>
|
<string name="appearance_monet">Dynamic Theme</string>
|
||||||
|
@ -21,4 +22,7 @@
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
|
|
||||||
<string name="unknown">Unknown</string>
|
<string name="unknown">Unknown</string>
|
||||||
|
|
||||||
|
<string name="error">An error occurred</string>
|
||||||
|
<string name="error_location">Couldn\'t retrieve location. Make sure to grant permission and enable GPS.</string>
|
||||||
</resources>
|
</resources>
|
Reference in a new issue