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.feeds.FeedScreenModel
import com.readrops.app.item.ItemScreenModel
import com.readrops.app.more.preferences.PreferencesScreenModel
import com.readrops.app.notifications.NotificationsScreenModel
import com.readrops.app.repositories.BaseRepository
import com.readrops.app.repositories.FreshRSSRepository
@ -51,6 +52,8 @@ val composeAppModule = module {
factory { (account: Account) -> NotificationsScreenModel(account, get()) }
factory { PreferencesScreenModel(get()) }
single { GetFoldersWithFeeds(get()) }
// 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.spacing
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
object MoreTab : Tab, KoinComponent {
@ -140,7 +139,7 @@ object MoreTab : Tab, KoinComponent {
spacing = MaterialTheme.spacing.largeSpacing,
padding = MaterialTheme.spacing.mediumSpacing,
tint = MaterialTheme.colorScheme.primary,
onClick = { navigator.push(PreferencesScreen(get())) }
onClick = { navigator.push(PreferencesScreen()) }
)
SelectableIconText(

View File

@ -12,25 +12,30 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
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.currentOrThrow
import com.readrops.app.R
import com.readrops.app.more.preferences.components.ListPreferenceWidget
import com.readrops.app.more.preferences.components.PreferenceHeader
import com.readrops.app.more.preferences.components.SwitchPreferenceWidget
import com.readrops.app.util.Preferences
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)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel = getScreenModel<PreferencesScreenModel>()
val state by screenModel.state.collectAsStateWithLifecycle()
Scaffold(
topBar = {
@ -52,11 +57,19 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
Box(
modifier = Modifier.padding(paddingValues)
) {
when (state) {
is PreferencesScreenState.Loading -> {
CenteredProgressIndicator()
}
else -> {
val loadedState = (state as PreferencesScreenState.Loaded)
Column {
PreferenceHeader(text = stringResource(id = R.string.global))
ListPreferenceWidget(
preference = preferences.theme,
preference = loadedState.themePref.second,
selectedKey = loadedState.themePref.first,
entries = mapOf(
"light" to stringResource(id = R.string.light),
"dark" to stringResource(id = R.string.dark),
@ -67,7 +80,8 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
)
ListPreferenceWidget(
preference = preferences.backgroundSynchronization,
preference = loadedState.backgroundSyncPref.second,
selectedKey = loadedState.backgroundSyncPref.first,
entries = mapOf(
"manual" to stringResource(id = R.string.manual),
"0.30" to stringResource(id = R.string.min_30),
@ -82,23 +96,26 @@ class PreferencesScreen(val preferences: Preferences) : AndroidScreen(), KoinCom
onValueChange = {}
)
PreferenceHeader(text = "Timeline")
PreferenceHeader(text = stringResource(id = R.string.timeline))
SwitchPreferenceWidget(
preference = preferences.hideReadFeeds,
preference = loadedState.hideReadFeeds.second,
isChecked = loadedState.hideReadFeeds.first,
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(
preference = preferences.scrollRead,
preference = loadedState.scrollReadPref.second,
isChecked = loadedState.scrollReadPref.first,
title = stringResource(id = R.string.mark_items_read)
)
PreferenceHeader(text = "Item view")
PreferenceHeader(text = stringResource(id = R.string.item_view))
ListPreferenceWidget(
preference = preferences.openLinksWith,
preference = loadedState.openLinksWith.second,
selectedKey = loadedState.openLinksWith.first,
entries = mapOf(
"navigator_view" to stringResource(id = R.string.navigator_view),
"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.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.readrops.app.util.Preference
import kotlinx.coroutines.launch
@Composable
fun <T> ListPreferenceWidget(
preference: Preference<T>,
selectedKey: T,
entries: Map<T, String>,
title: String,
modifier: Modifier = Modifier,
@ -22,7 +22,6 @@ fun <T> ListPreferenceWidget(
) {
val coroutineScope = rememberCoroutineScope()
var showDialog by remember { mutableStateOf(false) }
val selectedKey by preference.flow.collectAsStateWithLifecycle(initialValue = preference.default)
if (showDialog) {
val values = remember {

View File

@ -15,6 +15,9 @@ fun PreferenceHeader(
text = text,
style = MaterialTheme.typography.bodySmall,
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.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import com.readrops.app.util.Preference
@ -12,11 +10,11 @@ import kotlinx.coroutines.launch
@Composable
fun SwitchPreferenceWidget(
preference: Preference<Boolean>,
isChecked: Boolean,
title: String,
modifier: Modifier = Modifier,
subtitle: String? = null,
) {
val isChecked by preference.flow.collectAsState(initial = preference.default)
val coroutineScope = rememberCoroutineScope()
BasePreference(

View File

@ -177,4 +177,7 @@
<string name="url">URL</string>
<string name="unread">%1$d non-lu(s)</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>

View File

@ -186,4 +186,7 @@
<string name="url">URL</string>
<string name="unread">%1$d unread</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>