diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt index fe187b80..54c68508 100644 --- a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.flow @@ -15,6 +16,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlin.reflect.KClass import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -46,6 +48,12 @@ context(viewModel: ViewModel) fun Flow.listenValue(action: suspend (T) -> Unit): Job = listenValue(viewModel.viewModelScope, action) +context(viewModel: ViewModel) +fun MutableSharedFlow.emitOnMain(value: T) { + val flow = this + viewModel.viewModelScope.launch { flow.emit(value) } +} + fun Flow.listenValue( coroutineScope: CoroutineScope, action: suspend (T) -> Unit diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt index 2e3c10d1..1f13eb44 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt @@ -29,6 +29,7 @@ import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.database.AccountEntity import dev.meloda.fast.settings.model.HapticType import dev.meloda.fast.settings.model.SettingsDialog +import dev.meloda.fast.settings.model.SettingsEffect import dev.meloda.fast.settings.model.SettingsIntent import dev.meloda.fast.settings.model.SettingsItem import dev.meloda.fast.settings.model.SettingsNavigationIntent @@ -36,11 +37,10 @@ import dev.meloda.fast.settings.model.SettingsScreenState import dev.meloda.fast.settings.model.TextProvider import dev.meloda.fast.ui.R import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class SettingsViewModel( @@ -56,11 +56,8 @@ class SettingsViewModel( private val screenState = MutableStateFlow(SettingsScreenState.EMPTY) val screenStateFlow get() = screenState.asStateFlow() - private val hapticType = MutableStateFlow(null) - val hapticTypeFlow get() = hapticType.asStateFlow() - - private val navigationIntent = MutableStateFlow(null) - val navigationIntentFlow get() = navigationIntent.asStateFlow() + private val screenEffect = MutableSharedFlow(extraBufferCapacity = 1) + val screenEffectFlow = screenEffect.asSharedFlow() private val settings = mutableListOf>() private var showDebugCategory: Boolean = userSettings.showDebugCategory.value @@ -72,11 +69,7 @@ class SettingsViewModel( fun handleIntent(intent: SettingsIntent) { when (intent) { SettingsIntent.BackClick -> { - navigationIntent.setValue { SettingsNavigationIntent.Back } - } - - SettingsIntent.ConsumePerformHaptic -> { - hapticType.setValue { null } + screenEffect.tryEmit(SettingsEffect.Navigate(SettingsNavigationIntent.Back)) } is SettingsIntent.ItemClick -> { @@ -148,7 +141,10 @@ class SettingsViewModel( UserConfig.accessToken = oldToken }, success = { user -> - if (user == null) return@listenValue + if (user == null) { + UserConfig.accessToken = oldToken + return@listenValue + } UserConfig.currentUserId = user.id @@ -169,7 +165,9 @@ class SettingsViewModel( accountsRepository.storeAccounts(listOf(account)) - navigationIntent.setValue { SettingsNavigationIntent.Restart } + screenEffect.tryEmit( + SettingsEffect.Navigate(SettingsNavigationIntent.Restart) + ) } ) } @@ -204,26 +202,21 @@ class SettingsViewModel( private fun onLogOutAlertPositiveClick() { viewModelScope.launch(Dispatchers.IO) { - val tasks = listOf( - async { - accountsRepository.storeAccounts( - listOf( - AccountEntity( - userId = UserConfig.userId, - accessToken = "", - fastToken = UserConfig.fastToken, - trustedHash = UserConfig.trustedHash, - exchangeToken = null - ) - ) + accountsRepository.storeAccounts( + listOf( + AccountEntity( + userId = UserConfig.userId, + accessToken = "", + fastToken = UserConfig.fastToken, + trustedHash = UserConfig.trustedHash, + exchangeToken = null ) - }, - async { UserConfig.clear() } + ) ) - tasks.awaitAll() + UserConfig.clear() - navigationIntent.setValue { SettingsNavigationIntent.LogOut } + screenEffect.tryEmit(SettingsEffect.Navigate(SettingsNavigationIntent.LogOut)) } } @@ -263,7 +256,7 @@ class SettingsViewModel( createSettings() - hapticType.update { HapticType.REJECT } + screenEffect.tryEmit(SettingsEffect.PerformHaptic(HapticType.REJECT)) showDebugCategory = false } } @@ -279,7 +272,7 @@ class SettingsViewModel( createSettings() - hapticType.update { HapticType.LONG_PRESS } + screenEffect.tryEmit(SettingsEffect.PerformHaptic(HapticType.LONG_PRESS)) showDebugCategory = true } } diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsEffect.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsEffect.kt new file mode 100644 index 00000000..27870dae --- /dev/null +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsEffect.kt @@ -0,0 +1,6 @@ +package dev.meloda.fast.settings.model + +sealed interface SettingsEffect { + data class Navigate(val intent: SettingsNavigationIntent) : SettingsEffect + data class PerformHaptic(val type: HapticType) : SettingsEffect +} diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt index b7a8ae9b..6d7ea1a1 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt @@ -9,8 +9,6 @@ sealed class SettingsIntent { data class ItemLongClick(val key: String) : SettingsIntent() data class ItemValueChanged(val key: String, val newValue: Any?) : SettingsIntent() - data object ConsumePerformHaptic : SettingsIntent() - sealed class Dialog : SettingsIntent() { data object Dismiss : Dialog() data class ConfirmClick(val bundle: Bundle? = null) : Dialog() diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/navigation/SettingsNavigation.kt index 1f374de5..6641f46d 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/navigation/SettingsNavigation.kt @@ -2,13 +2,18 @@ package dev.meloda.fast.settings.navigation import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalView import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.settings.SettingsViewModel +import dev.meloda.fast.settings.model.SettingsEffect import dev.meloda.fast.settings.model.SettingsNavigationIntent import dev.meloda.fast.settings.presentation.SettingsRoute +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import kotlinx.serialization.Serializable import org.koin.androidx.compose.koinViewModel @@ -19,19 +24,26 @@ fun NavGraphBuilder.settingsScreen( handleNavigationIntent: (SettingsNavigationIntent) -> Unit ) { composable { + val view = LocalView.current val viewModel: SettingsViewModel = koinViewModel() val screenState by viewModel.screenStateFlow.collectAsStateWithLifecycle() - val hapticType by viewModel.hapticTypeFlow.collectAsStateWithLifecycle() - val navigationIntent by viewModel.navigationIntentFlow.collectAsStateWithLifecycle() - LaunchedEffect(navigationIntent) { - navigationIntent?.let(handleNavigationIntent) + LaunchedEffect(Unit) { + viewModel.screenEffectFlow.onEach { effect -> + when (effect) { + is SettingsEffect.Navigate -> handleNavigationIntent(effect.intent) + is SettingsEffect.PerformHaptic -> { + if (AppSettings.General.enableHaptic) { + view.performHapticFeedback(effect.type.getHaptic()) + } + } + } + }.collect() } SettingsRoute( handleIntent = viewModel::handleIntent, screenState = screenState, - hapticType = hapticType ) } } diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsRoute.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsRoute.kt index 0d2141fd..fa15b7cb 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsRoute.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsRoute.kt @@ -1,7 +1,6 @@ package dev.meloda.fast.settings.presentation import androidx.compose.runtime.Composable -import dev.meloda.fast.settings.model.HapticType import dev.meloda.fast.settings.model.SettingsIntent import dev.meloda.fast.settings.model.SettingsScreenState @@ -9,12 +8,10 @@ import dev.meloda.fast.settings.model.SettingsScreenState fun SettingsRoute( handleIntent: (SettingsIntent) -> Unit, screenState: SettingsScreenState, - hapticType: HapticType? ) { SettingsScreen( handleIntent = handleIntent, screenState = screenState, - hapticType = hapticType ) HandleDialogs( diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt index 8fcacc52..9a7505e9 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt @@ -21,12 +21,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -36,8 +34,6 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -import dev.meloda.fast.datastore.AppSettings -import dev.meloda.fast.settings.model.HapticType import dev.meloda.fast.settings.model.SettingsIntent import dev.meloda.fast.settings.model.SettingsScreenState import dev.meloda.fast.settings.model.UiItem @@ -58,10 +54,7 @@ import dev.meloda.fast.ui.theme.LocalThemeConfig fun SettingsScreen( handleIntent: (SettingsIntent) -> Unit, screenState: SettingsScreenState = SettingsScreenState.EMPTY, - hapticType: HapticType? ) { - val view = LocalView.current - val onSettingsItemClicked by rememberUpdatedState { key: String -> handleIntent(SettingsIntent.ItemClick(key)) } @@ -74,15 +67,6 @@ fun SettingsScreen( handleIntent(SettingsIntent.ItemValueChanged(key, newValue)) } - LaunchedEffect(hapticType) { - if (hapticType != null) { - if (AppSettings.General.enableHaptic) { - view.performHapticFeedback(hapticType.getHaptic()) - } - handleIntent(SettingsIntent.ConsumePerformHaptic) - } - } - val themeConfig = LocalThemeConfig.current val hazeState = remember { HazeState(true) }