Finish up multiple locations support
This commit is contained in:
parent
8ff9b5ff4d
commit
edeea501eb
9 changed files with 82 additions and 72 deletions
|
@ -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) }
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 }
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue