Improve PreferencesScreen state management

This commit is contained in:
Shinokuni 2024-07-13 19:56:23 +02:00
parent da5b030ab6
commit 965fb2ea67
9 changed files with 142 additions and 58 deletions

View File

@ -15,6 +15,7 @@ import com.readrops.app.account.credentials.AccountCredentialsScreenModel
import com.readrops.app.account.selection.AccountSelectionScreenModel import com.readrops.app.account.selection.AccountSelectionScreenModel
import com.readrops.app.feeds.FeedScreenModel import com.readrops.app.feeds.FeedScreenModel
import com.readrops.app.item.ItemScreenModel import com.readrops.app.item.ItemScreenModel
import com.readrops.app.more.preferences.PreferencesScreenModel
import com.readrops.app.notifications.NotificationsScreenModel import com.readrops.app.notifications.NotificationsScreenModel
import com.readrops.app.repositories.BaseRepository import com.readrops.app.repositories.BaseRepository
import com.readrops.app.repositories.FreshRSSRepository import com.readrops.app.repositories.FreshRSSRepository
@ -51,6 +52,8 @@ val composeAppModule = module {
factory { (account: Account) -> NotificationsScreenModel(account, get()) } factory { (account: Account) -> NotificationsScreenModel(account, get()) }
factory { PreferencesScreenModel(get()) }
single { GetFoldersWithFeeds(get()) } single { GetFoldersWithFeeds(get()) }
// repositories // repositories

View File

@ -36,7 +36,6 @@ import com.readrops.app.util.theme.MediumSpacer
import com.readrops.app.util.theme.ShortSpacer import com.readrops.app.util.theme.ShortSpacer
import com.readrops.app.util.theme.spacing import com.readrops.app.util.theme.spacing
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get
object MoreTab : Tab, KoinComponent { object MoreTab : Tab, KoinComponent {
@ -140,7 +139,7 @@ object MoreTab : Tab, KoinComponent {
spacing = MaterialTheme.spacing.largeSpacing, spacing = MaterialTheme.spacing.largeSpacing,
padding = MaterialTheme.spacing.mediumSpacing, padding = MaterialTheme.spacing.mediumSpacing,
tint = MaterialTheme.colorScheme.primary, tint = MaterialTheme.colorScheme.primary,
onClick = { navigator.push(PreferencesScreen(get())) } onClick = { navigator.push(PreferencesScreen()) }
) )
SelectableIconText( SelectableIconText(

View File

@ -12,25 +12,30 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.readrops.app.R import com.readrops.app.R
import com.readrops.app.more.preferences.components.ListPreferenceWidget import com.readrops.app.more.preferences.components.ListPreferenceWidget
import com.readrops.app.more.preferences.components.PreferenceHeader import com.readrops.app.more.preferences.components.PreferenceHeader
import com.readrops.app.more.preferences.components.SwitchPreferenceWidget import com.readrops.app.more.preferences.components.SwitchPreferenceWidget
import com.readrops.app.util.Preferences
import com.readrops.app.util.components.AndroidScreen import com.readrops.app.util.components.AndroidScreen
import org.koin.core.component.KoinComponent import com.readrops.app.util.components.CenteredProgressIndicator
class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinComponent { class PreferencesScreen : AndroidScreen() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val screenModel = getScreenModel<PreferencesScreenModel>()
val state by screenModel.state.collectAsStateWithLifecycle()
Scaffold( Scaffold(
topBar = { topBar = {
@ -52,11 +57,19 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
Box( Box(
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {
when (state) {
is PreferencesScreenState.Loading -> {
CenteredProgressIndicator()
}
else -> {
val loadedState = (state as PreferencesScreenState.Loaded)
Column { Column {
PreferenceHeader(text = stringResource(id = R.string.global)) PreferenceHeader(text = stringResource(id = R.string.global))
ListPreferenceWidget( ListPreferenceWidget(
preference = preferences.theme, preference = loadedState.themePref.second,
selectedKey = loadedState.themePref.first,
entries = mapOf( entries = mapOf(
"light" to stringResource(id = R.string.light), "light" to stringResource(id = R.string.light),
"dark" to stringResource(id = R.string.dark), "dark" to stringResource(id = R.string.dark),
@ -67,7 +80,8 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
) )
ListPreferenceWidget( ListPreferenceWidget(
preference = preferences.backgroundSynchronization, preference = loadedState.backgroundSyncPref.second,
selectedKey = loadedState.backgroundSyncPref.first,
entries = mapOf( entries = mapOf(
"manual" to stringResource(id = R.string.manual), "manual" to stringResource(id = R.string.manual),
"0.30" to stringResource(id = R.string.min_30), "0.30" to stringResource(id = R.string.min_30),
@ -82,23 +96,26 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
onValueChange = {} onValueChange = {}
) )
PreferenceHeader(text = "Timeline") PreferenceHeader(text = stringResource(id = R.string.timeline))
SwitchPreferenceWidget( SwitchPreferenceWidget(
preference = preferences.hideReadFeeds, preference = loadedState.hideReadFeeds.second,
isChecked = loadedState.hideReadFeeds.first,
title = stringResource(id = R.string.hide_feeds), title = stringResource(id = R.string.hide_feeds),
subtitle = "Feeds with no left unread items will be hidden with their respective folder" subtitle = stringResource(R.string.hide_feeds_subtitle)
) )
SwitchPreferenceWidget( SwitchPreferenceWidget(
preference = preferences.scrollRead, preference = loadedState.scrollReadPref.second,
isChecked = loadedState.scrollReadPref.first,
title = stringResource(id = R.string.mark_items_read) title = stringResource(id = R.string.mark_items_read)
) )
PreferenceHeader(text = "Item view") PreferenceHeader(text = stringResource(id = R.string.item_view))
ListPreferenceWidget( ListPreferenceWidget(
preference = preferences.openLinksWith, preference = loadedState.openLinksWith.second,
selectedKey = loadedState.openLinksWith.first,
entries = mapOf( entries = mapOf(
"navigator_view" to stringResource(id = R.string.navigator_view), "navigator_view" to stringResource(id = R.string.navigator_view),
"external_navigator" to stringResource(id = R.string.external_navigator) "external_navigator" to stringResource(id = R.string.external_navigator)
@ -110,5 +127,7 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
} }
} }
} }
}
}
} }

View File

@ -0,0 +1,57 @@
package com.readrops.app.more.preferences
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.readrops.app.util.Preference
import com.readrops.app.util.Preferences
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
typealias PreferenceState<T> = Pair<T, Preference<T>>
class PreferencesScreenModel(
preferences: Preferences,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) : StateScreenModel<PreferencesScreenState>(PreferencesScreenState.Loading) {
init {
screenModelScope.launch(dispatcher) {
combine(
preferences.theme.flow,
preferences.backgroundSynchronization.flow,
preferences.scrollRead.flow,
preferences.hideReadFeeds.flow,
preferences.openLinksWith.flow
) { (theme, backgroundSync, scrollRead, hideReadFeeds, openLinksWith) ->
PreferencesScreenState.Loaded(
themePref = (theme as String) to preferences.theme,
backgroundSyncPref = (backgroundSync as String) to preferences.backgroundSynchronization,
scrollReadPref = (scrollRead as Boolean) to preferences.scrollRead,
hideReadFeeds = (hideReadFeeds as Boolean) to preferences.hideReadFeeds,
openLinksWith = (openLinksWith as String) to preferences.openLinksWith
)
}.collect { theme ->
mutableState.update { theme }
}
}
}
}
sealed class PreferencesScreenState {
data object Loading : PreferencesScreenState()
data object Error : PreferencesScreenState()
data class Loaded(
val themePref: PreferenceState<String>,
val backgroundSyncPref: PreferenceState<String>,
val scrollReadPref: PreferenceState<Boolean>,
val hideReadFeeds: PreferenceState<Boolean>,
val openLinksWith: PreferenceState<String>
) : PreferencesScreenState()
}

View File

@ -8,13 +8,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.readrops.app.util.Preference import com.readrops.app.util.Preference
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun <T> ListPreferenceWidget( fun <T> ListPreferenceWidget(
preference: Preference<T>, preference: Preference<T>,
selectedKey: T,
entries: Map<T, String>, entries: Map<T, String>,
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -22,7 +22,6 @@ fun <T> ListPreferenceWidget(
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
val selectedKey by preference.flow.collectAsStateWithLifecycle(initialValue = preference.default)
if (showDialog) { if (showDialog) {
val values = remember { val values = remember {

View File

@ -15,6 +15,9 @@ fun PreferenceHeader(
text = text, text = text,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(MaterialTheme.spacing.shortSpacing) modifier = Modifier.padding(
horizontal = MaterialTheme.spacing.mediumSpacing,
vertical = MaterialTheme.spacing.shortSpacing
)
) )
} }

View File

@ -2,8 +2,6 @@ package com.readrops.app.more.preferences.components
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.readrops.app.util.Preference import com.readrops.app.util.Preference
@ -12,11 +10,11 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun SwitchPreferenceWidget( fun SwitchPreferenceWidget(
preference: Preference<Boolean>, preference: Preference<Boolean>,
isChecked: Boolean,
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
subtitle: String? = null, subtitle: String? = null,
) { ) {
val isChecked by preference.flow.collectAsState(initial = preference.default)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
BasePreference( BasePreference(

View File

@ -177,4 +177,7 @@
<string name="url">URL</string> <string name="url">URL</string>
<string name="unread">%1$d non-lu(s)</string> <string name="unread">%1$d non-lu(s)</string>
<string name="preferences">Paramètres</string> <string name="preferences">Paramètres</string>
<string name="item_view">Vue article</string>
<string name="timeline">Timeline</string>
<string name="hide_feeds_subtitle">Les flux sans nouveaux articles disparaîtront du menu tiroir</string>
</resources> </resources>

View File

@ -186,4 +186,7 @@
<string name="url">URL</string> <string name="url">URL</string>
<string name="unread">%1$d unread</string> <string name="unread">%1$d unread</string>
<string name="preferences">Preferences</string> <string name="preferences">Preferences</string>
<string name="item_view">Article view</string>
<string name="timeline">Timeline</string>
<string name="hide_feeds_subtitle">Feeds with no left unread items will be hidden in the drawer</string>
</resources> </resources>