Finish up multiple locations support

This commit is contained in:
Henry Hiles 2023-05-06 11:02:38 -04:00
parent 8ff9b5ff4d
commit edeea501eb
9 changed files with 82 additions and 72 deletions

View file

@ -29,7 +29,7 @@ abstract class BasePreferenceManager(
enumValueOf<E>(getString(key, defaultValue.name)) enumValueOf<E>(getString(key, defaultValue.name))
protected inline fun <reified T> getJson(key: String, defaultValue: T) = protected inline fun <reified T> getJson(key: String, defaultValue: T) =
Json.decodeFromString<T>(getString(key, null)) ?: defaultValue Json.decodeFromString<T>(getString(key, Json.encodeToString(defaultValue)))
protected fun putString(key: String, value: String?) = prefs.edit { putString(key, value) } protected fun putString(key: String, value: String?) = prefs.edit { putString(key, value) }
private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) } private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) }

View file

@ -19,53 +19,68 @@ import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManage
import org.koin.androidx.compose.get import org.koin.androidx.compose.get
@Composable @Composable
fun LocationsDrawer(drawerState: DrawerState, children: @Composable () -> Unit) { fun LocationsDrawer(
val location: LocationPreferenceManager = get() drawerState: DrawerState,
onClose: () -> Unit,
children: @Composable () -> Unit
) {
val locationPreferenceManager: LocationPreferenceManager = get()
val navigator = LocalNavigator.current?.parent val navigator = LocalNavigator.current?.parent
ModalNavigationDrawer(drawerContent = { ModalNavigationDrawer(
ModalDrawerSheet { drawerContent = {
Column(modifier = Modifier.padding(16.dp)) { ModalDrawerSheet {
val locations = location.locations Column(modifier = Modifier.padding(16.dp)) {
val locations = locationPreferenceManager.locations
Text( Text(
text = stringResource(id = R.string.locations), text = stringResource(id = R.string.locations),
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
) )
Spacer(modifier = Modifier.height(16.dp))
locations.forEachIndexed { index, data -> Spacer(modifier = Modifier.height(16.dp))
NavigationDrawerItem( locations.forEachIndexed { index, data ->
label = { Text(text = data.location) }, val selected = index == locationPreferenceManager.selectedIndex
selected = index == location.selectedIndex, NavigationDrawerItem(
onClick = { location.selectedIndex = index }, label = { Text(text = data.location) },
badge = { selected = selected,
IconButton(onClick = { location.locations -= data }) { onClick = {
Icon( onClose()
imageVector = Icons.Default.Delete, locationPreferenceManager.selectedIndex = index
contentDescription = stringResource( },
id = R.string.action_delete badge = {
IconButton(onClick = {
locationPreferenceManager.locations -= data
if (selected) locationPreferenceManager.selectedIndex = 0
}) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = stringResource(
id = R.string.action_delete
)
) )
) }
} }
} )
}
Spacer(modifier = Modifier.weight(1f))
NavigationDrawerItem(
label = { Text(text = stringResource(id = R.string.location_add)) },
icon = {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(id = R.string.location_add)
)
},
selected = true,
onClick = { navigator?.push(LocationPickerScreen()) },
) )
} }
Spacer(modifier = Modifier.weight(1f))
NavigationDrawerItem(
label = { Text(text = stringResource(id = R.string.location_add)) },
icon = {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(id = R.string.location_add)
)
},
selected = true,
onClick = { navigator?.push(LocationPickerScreen()) },
)
} }
} },
}, drawerState = drawerState) { drawerState = drawerState
) {
children() children()
} }
} }

View file

@ -1,8 +1,6 @@
package com.henryhiles.qweather.presentation.components.weather package com.henryhiles.qweather.presentation.components.weather
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee
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.Icons
@ -21,9 +19,8 @@ import androidx.compose.ui.unit.sp
import com.henryhiles.qweather.domain.weather.HourlyWeatherData import com.henryhiles.qweather.domain.weather.HourlyWeatherData
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier = Modifier) { fun WeatherCard(hour: HourlyWeatherData?, modifier: Modifier = Modifier) {
hour?.let { hour?.let {
val formattedTime = remember(it) { val formattedTime = remember(it) {
it.time.format(DateTimeFormatter.ofPattern("HH:mm")) it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
@ -42,13 +39,6 @@ fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier =
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text(
text = location,
modifier = Modifier
.width(152.dp)
.basicMarquee(delayMillis = 2000, initialDelayMillis = 1000),
maxLines = 1,
)
Text( Text(
text = "Today $formattedTime", text = "Today $formattedTime",
) )

View file

@ -44,6 +44,7 @@ class LocationPickerScreen : Screen {
FloatingActionButton(onClick = { FloatingActionButton(onClick = {
location?.let { location?.let {
screenModel.prefs.locations += it screenModel.prefs.locations += it
screenModel.prefs.selectedIndex = screenModel.prefs.locations.count() - 1
navigator?.push(MainScreen()) navigator?.push(MainScreen())
} ?: kotlin.run { isAboutOpen = true } } ?: kotlin.run { isAboutOpen = true }
}) { }) {

View file

@ -1,21 +1,18 @@
package com.henryhiles.qweather.presentation.screen package com.henryhiles.qweather.presentation.screen
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.CurrentScreen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.navigator.tab.TabNavigator
import com.henryhiles.qweather.R import com.henryhiles.qweather.R
import com.henryhiles.qweather.domain.util.NavigationTab import com.henryhiles.qweather.domain.util.NavigationTab
@ -28,26 +25,34 @@ import kotlinx.coroutines.launch
import org.koin.androidx.compose.get import org.koin.androidx.compose.get
class MainScreen : Screen { class MainScreen : Screen {
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val locationPreferenceManager: LocationPreferenceManager = get()
val drawerState = val drawerState =
rememberDrawerState(initialValue = DrawerValue.Closed) rememberDrawerState(initialValue = DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
TabNavigator(tab = TodayTab) { TabNavigator(tab = TodayTab) {
LocationsDrawer(drawerState = drawerState) { LocationsDrawer(
drawerState = drawerState,
onClose = { coroutineScope.launch { drawerState.close() } }) {
Scaffold( Scaffold(
topBar = { topBar = {
SmallToolbar( SmallToolbar(
title = { Text(text = stringResource(R.string.app_name)) }, title = {
with(locationPreferenceManager) {
Text(
text = locations[selectedIndex].location,
maxLines = 1,
modifier = Modifier.basicMarquee()
)
}
},
actions = { actions = {
(it.current as? NavigationTab)?.Actions() (it.current as? NavigationTab)?.Actions()
} }
) { ) {
IconButton(onClick = { IconButton(onClick = { coroutineScope.launch { drawerState.open() } }) {
coroutineScope.launch {
with(drawerState) { if (isOpen) close() else open() }
}
}) {
Icon( Icon(
imageVector = Icons.Default.Menu, imageVector = Icons.Default.Menu,
contentDescription = stringResource(id = R.string.location_picker_open) contentDescription = stringResource(id = R.string.location_picker_open)

View file

@ -19,13 +19,13 @@ data class DailyWeatherState(
class DailyWeatherScreenModel( class DailyWeatherScreenModel(
private val repository: WeatherRepository, private val repository: WeatherRepository,
locationPreferenceManager: LocationPreferenceManager val locationPreferenceManager: LocationPreferenceManager
) : ScreenModel { ) : ScreenModel {
var state by mutableStateOf(DailyWeatherState()) var state by mutableStateOf(DailyWeatherState())
private set private set
val location = locationPreferenceManager.locations[locationPreferenceManager.selectedIndex]
fun loadWeatherInfo(cache: Boolean = true) { fun loadWeatherInfo(cache: Boolean = true) {
val location = locationPreferenceManager.locations[locationPreferenceManager.selectedIndex]
coroutineScope.launch { coroutineScope.launch {
state = state.copy(isLoading = true, error = null) state = state.copy(isLoading = true, error = null)
state = when (val result = repository.getDailyWeatherData( state = when (val result = repository.getDailyWeatherData(

View file

@ -19,14 +19,13 @@ data class HourlyWeatherState(
class HourlyWeatherScreenModel( class HourlyWeatherScreenModel(
private val repository: WeatherRepository, private val repository: WeatherRepository,
locationPreferenceManager: LocationPreferenceManager, val locationPreferenceManager: LocationPreferenceManager,
) : ScreenModel { ) : ScreenModel {
var state by mutableStateOf(HourlyWeatherState()) var state by mutableStateOf(HourlyWeatherState())
private set private set
val location = locationPreferenceManager.locations[locationPreferenceManager.selectedIndex]
fun loadWeatherInfo(cache: Boolean = true) { fun loadWeatherInfo(cache: Boolean = true) {
val location = locationPreferenceManager.locations[locationPreferenceManager.selectedIndex]
coroutineScope.launch { coroutineScope.launch {
state = state.copy(isLoading = true, error = null, selected = null) state = state.copy(isLoading = true, error = null, selected = null)
state = when (val result = state = when (val result =

View file

@ -43,7 +43,7 @@ object TodayTab : NavigationTab {
override fun Content() { override fun Content() {
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>() val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
LaunchedEffect(key1 = false) { LaunchedEffect(key1 = weatherViewModel.locationPreferenceManager.selectedIndex) {
weatherViewModel.loadWeatherInfo() weatherViewModel.loadWeatherInfo()
} }
@ -78,13 +78,13 @@ object TodayTab : NavigationTab {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
WeatherCard( WeatherCard(
hour = weatherViewModel.state.selected?.let { hour = weatherViewModel.state.selected?.let {
weatherViewModel.state.hourlyWeatherInfo?.weatherData?.get(it) weatherViewModel.state.hourlyWeatherInfo?.weatherData?.get(it)
} ?: weatherViewModel.state.hourlyWeatherInfo?.currentWeatherData, } ?: weatherViewModel.state.hourlyWeatherInfo?.currentWeatherData,
location = weatherViewModel.location.location
) )
WeatherToday(state = weatherViewModel.state) WeatherToday(state = weatherViewModel.state)
WeatherForecast( WeatherForecast(

View file

@ -43,7 +43,7 @@ object WeekTab : NavigationTab {
override fun Content() { override fun Content() {
val weatherViewModel = getScreenModel<DailyWeatherScreenModel>() val weatherViewModel = getScreenModel<DailyWeatherScreenModel>()
LaunchedEffect(key1 = false) { LaunchedEffect(key1 = weatherViewModel.locationPreferenceManager.selectedIndex) {
weatherViewModel.loadWeatherInfo() weatherViewModel.loadWeatherInfo()
} }