From 2daab8d0f70cf1c6deeaf2e0fb842cadfd5e5985 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Sat, 30 May 2026 19:16:38 +0300 Subject: [PATCH] refactor(settings): route settings UI through intents and navigation effects --- .../meloda/fast/presentation/RootScreen.kt | 32 ++-- .../fast/domain/LongPollUpdatesParser.kt | 4 +- .../meloda/fast/settings/SettingsViewModel.kt | 145 ++++++++++++------ .../fast/settings/model/SettingsIntent.kt | 20 +++ .../fast/settings/model/SettingsItem.kt | 4 +- .../model/SettingsNavigationIntent.kt | 8 + .../settings/model/SettingsScreenState.kt | 4 +- .../settings/navigation/SettingsNavigation.kt | 27 +++- .../settings/presentation/SettingsDialogs.kt | 52 +++---- .../settings/presentation/SettingsRoute.kt | 59 ++----- .../settings/presentation/SettingsScreen.kt | 29 ++-- 11 files changed, 231 insertions(+), 153 deletions(-) create mode 100644 feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt create mode 100644 feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsNavigationIntent.kt diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt index bc1be759..939ff487 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt @@ -59,6 +59,7 @@ import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.navigation.Main import dev.meloda.fast.navigation.mainScreen import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog +import dev.meloda.fast.settings.model.SettingsNavigationIntent import dev.meloda.fast.settings.navigation.navigateToSettings import dev.meloda.fast.settings.navigation.settingsScreen import dev.meloda.fast.ui.R @@ -362,18 +363,31 @@ fun RootScreen( ) settingsScreen( - onBack = navController::navigateUp, - onLogOutButtonClicked = { navController.navigateToAuth(true) }, - onLanguageItemClicked = navController::navigateToLanguagePicker, - onRestartRequired = { - activity?.let { - val intent = Intent(activity, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - activity.startActivity(intent) - activity.finish() + handleNavigationIntent = { intent -> + when (intent) { + SettingsNavigationIntent.Back -> navController.navigateUp() + SettingsNavigationIntent.Language -> navController.navigateToLanguagePicker() + SettingsNavigationIntent.Restart -> { + activity?.let { + val intent = + Intent(activity, MainActivity::class.java) + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP + ) + activity.finish() + activity.startActivity(intent) + } + } + + SettingsNavigationIntent.LogOut -> { + navController.navigateToAuth(true) + } } } ) + languagePickerScreen(onBack = navController::navigateUp) } diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt index d8a94f9d..3f6ebe18 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt @@ -641,7 +641,7 @@ class LongPollUpdatesParser( ).listenValue(this) { state -> state.processState( error = { error -> - logger.error(this::class, "loadMessage(): ERROR: $error") + logger.error(this@LongPollUpdatesParser::class, "loadMessage(): ERROR: $error") continuation.resume(null) }, success = { response -> @@ -670,7 +670,7 @@ class LongPollUpdatesParser( ).listenValue(coroutineScope) { state -> state.processState( error = { error -> - logger.error(this::class, "loadConvo(): ERROR: $error") + logger.error(this@LongPollUpdatesParser::class, "loadConvo(): ERROR: $error") continuation.resume(null) }, success = { response -> 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 5ee54c7a..2e3c10d1 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 @@ -11,9 +11,10 @@ import dev.meloda.fast.common.VkConstants import dev.meloda.fast.common.extensions.findWithIndex import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue +import dev.meloda.fast.common.extensions.updateValue import dev.meloda.fast.common.model.DarkMode -import dev.meloda.fast.common.model.NetworkLogLevel import dev.meloda.fast.common.model.LongPollState +import dev.meloda.fast.common.model.NetworkLogLevel import dev.meloda.fast.common.model.UiText import dev.meloda.fast.common.model.parseString import dev.meloda.fast.data.UserConfig @@ -24,10 +25,13 @@ import dev.meloda.fast.datastore.SettingsKeys import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.domain.GetCurrentAccountUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase +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.SettingsIntent import dev.meloda.fast.settings.model.SettingsItem +import dev.meloda.fast.settings.model.SettingsNavigationIntent import dev.meloda.fast.settings.model.SettingsScreenState import dev.meloda.fast.settings.model.TextProvider import dev.meloda.fast.ui.R @@ -45,35 +49,83 @@ class SettingsViewModel( private val getCurrentAccountUseCase: GetCurrentAccountUseCase, private val userSettings: UserSettings, private val resources: Resources, - private val longPollController: LongPollController + private val longPollController: LongPollController, + private val logger: FastLogger ) : ViewModel() { - private val _screenState = MutableStateFlow(SettingsScreenState.EMPTY) - val screenState = _screenState.asStateFlow() + private val screenState = MutableStateFlow(SettingsScreenState.EMPTY) + val screenStateFlow get() = screenState.asStateFlow() - private val _hapticType = MutableStateFlow(null) - val hapticType = _hapticType.asStateFlow() + private val hapticType = MutableStateFlow(null) + val hapticTypeFlow get() = hapticType.asStateFlow() - private val _dialog = MutableStateFlow(null) - val dialog = _dialog.asStateFlow() + private val navigationIntent = MutableStateFlow(null) + val navigationIntentFlow get() = navigationIntent.asStateFlow() - private val _isNeedToRestart = MutableStateFlow(false) - val isNeedToRestart = _isNeedToRestart.asStateFlow() - - private val settings = MutableStateFlow>>(emptyList()) + private val settings = mutableListOf>() + private var showDebugCategory: Boolean = userSettings.showDebugCategory.value init { createSettings() } - fun onDialogConfirmed(dialog: SettingsDialog, bundle: Bundle) { - onDialogDismissed(dialog) + fun handleIntent(intent: SettingsIntent) { + when (intent) { + SettingsIntent.BackClick -> { + navigationIntent.setValue { SettingsNavigationIntent.Back } + } + + SettingsIntent.ConsumePerformHaptic -> { + hapticType.setValue { null } + } + + is SettingsIntent.ItemClick -> { + onSettingsItemClicked(intent.key) + } + + is SettingsIntent.ItemLongClick -> { + onSettingsItemLongClicked(intent.key) + } + + is SettingsIntent.ItemValueChanged -> { + onSettingsItemChanged(intent.key, intent.newValue) + } + + is SettingsIntent.Dialog -> { + when (intent) { + SettingsIntent.Dialog.CancelClick -> Unit + + is SettingsIntent.Dialog.ConfirmClick -> { + onDialogConfirmed(intent.bundle) + } + + SettingsIntent.Dialog.Dismiss -> { + onDialogDismissed() + } + + is SettingsIntent.Dialog.ItemPick -> { + onDialogItemPicked(intent.bundle) + } + } + } + } + } + + private fun setDialog(dialog: SettingsDialog?) { + screenState.updateValue { copy(dialog = dialog) } + } + + private fun onDialogConfirmed(bundle: Bundle?) { + val dialog = screenState.value.dialog ?: return + onDialogDismissed() when (dialog) { is SettingsDialog.LogOut -> onLogOutAlertPositiveClick() is SettingsDialog.PerformCrash -> onPerformCrashPositiveButtonClicked() is SettingsDialog.ImportAuthData -> { + if (bundle == null) return + val accessToken = bundle.getString("ACCESS_TOKEN") ?: return val exchangeToken = bundle.getString("EXCHANGE_TOKEN") val trustedHash = bundle.getString("TRUSTED_HASH") @@ -89,6 +141,10 @@ class SettingsViewModel( ).listenValue(viewModelScope) { state -> state.processState( error = { error -> + logger.error( + this@SettingsViewModel::class, + "importAuthInfo(): loadUserById(): ERROR: $error" + ) UserConfig.accessToken = oldToken }, success = { user -> @@ -113,7 +169,7 @@ class SettingsViewModel( accountsRepository.storeAccounts(listOf(account)) - _isNeedToRestart.setValue { true } + navigationIntent.setValue { SettingsNavigationIntent.Restart } } ) } @@ -124,7 +180,8 @@ class SettingsViewModel( } } - fun onDialogDismissed(dialog: SettingsDialog) { + private fun onDialogDismissed() { + val dialog = screenState.value.dialog ?: return when (dialog) { is SettingsDialog.LogOut -> Unit is SettingsDialog.PerformCrash -> Unit @@ -132,10 +189,11 @@ class SettingsViewModel( is SettingsDialog.ExportAuthData -> Unit } - _dialog.setValue { null } + setDialog(null) } - fun onDialogItemPicked(dialog: SettingsDialog, bundle: Bundle) { + private fun onDialogItemPicked(bundle: Bundle?) { + val dialog = screenState.value.dialog ?: return when (dialog) { is SettingsDialog.LogOut -> Unit is SettingsDialog.PerformCrash -> Unit @@ -144,7 +202,7 @@ class SettingsViewModel( } } - fun onLogOutAlertPositiveClick() { + private fun onLogOutAlertPositiveClick() { viewModelScope.launch(Dispatchers.IO) { val tasks = listOf( async { @@ -164,35 +222,37 @@ class SettingsViewModel( ) tasks.awaitAll() + + navigationIntent.setValue { SettingsNavigationIntent.LogOut } } } - fun onPerformCrashPositiveButtonClicked() { + private fun onPerformCrashPositiveButtonClicked() { throw Exception("Test exception") } - fun onSettingsItemClicked(key: String) { + private fun onSettingsItemClicked(key: String) { when (key) { SettingsKeys.KEY_ACCOUNT_LOGOUT -> { - _dialog.setValue { SettingsDialog.LogOut } + setDialog(SettingsDialog.LogOut) } SettingsKeys.KEY_DEBUG_PERFORM_CRASH -> { - _dialog.setValue { SettingsDialog.PerformCrash } + setDialog(SettingsDialog.PerformCrash) } SettingsKeys.KEY_DEBUG_IMPORT_AUTH_DATA -> { - _dialog.setValue { SettingsDialog.ImportAuthData } + setDialog(SettingsDialog.ImportAuthData) } SettingsKeys.KEY_DEBUG_EXPORT_AUTH_DATA -> { - _dialog.setValue { + setDialog( SettingsDialog.ExportAuthData( accessToken = UserConfig.accessToken, exchangeToken = UserConfig.exchangeToken, trustedHash = UserConfig.trustedHash ) - } + ) } SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST -> { @@ -203,13 +263,13 @@ class SettingsViewModel( createSettings() - _hapticType.update { HapticType.REJECT } - _screenState.setValue { old -> old.copy(showDebugOptions = false) } + hapticType.update { HapticType.REJECT } + showDebugCategory = false } } } - fun onSettingsItemLongClicked(key: String) { + private fun onSettingsItemLongClicked(key: String) { when (key) { SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> { if (AppSettings.Debug.showDebugCategory) return @@ -219,18 +279,18 @@ class SettingsViewModel( createSettings() - _hapticType.update { HapticType.LONG_PRESS } - _screenState.setValue { old -> old.copy(showDebugOptions = true) } + hapticType.update { HapticType.LONG_PRESS } + showDebugCategory = true } } } - fun onSettingsItemChanged(key: String, newValue: Any?) { - settings.value.findWithIndex { it.key == key }?.let { (index, item) -> + private fun onSettingsItemChanged(key: String, newValue: Any?) { + settings.findWithIndex { it.key == key }?.let { (index, item) -> item.updateValue(newValue) item.updateText() - _screenState.setValue { old -> + screenState.setValue { old -> old.copy( settings = old.settings.toMutableList().apply { this[index] = item.asPresentation(resources) @@ -311,10 +371,6 @@ class SettingsViewModel( } } - fun onHapticPerformed() { - _hapticType.update { null } - } - private fun createSettings() { val accountVisible = UserConfig.isLoggedIn() val accountTitle = SettingsItem.Title( @@ -512,7 +568,8 @@ class SettingsViewModel( values = logLevelValues.keys.toList().map(NetworkLogLevel::value) ).apply { textProvider = TextProvider { item -> - val textValue = logLevelValues[NetworkLogLevel.parse(item.value)].parseString(resources) + val textValue = + logLevelValues[NetworkLogLevel.parse(item.value)].parseString(resources) UiText.Simple("Current value: $textValue") } @@ -602,12 +659,10 @@ class SettingsViewModel( } private fun emitSettings(newSettings: List>) { - settings.update { newSettings } + settings.clear() + settings.addAll(newSettings) - val uiSettings = newSettings.map { item -> - item.asPresentation(resources) - } - - _screenState.setValue { old -> old.copy(settings = uiSettings) } + val uiSettings = newSettings.map { it.asPresentation(resources) } + screenState.setValue { old -> old.copy(settings = uiSettings) } } } 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 new file mode 100644 index 00000000..b7a8ae9b --- /dev/null +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsIntent.kt @@ -0,0 +1,20 @@ +package dev.meloda.fast.settings.model + +import android.os.Bundle + +sealed class SettingsIntent { + data object BackClick : SettingsIntent() + + data class ItemClick(val key: String) : 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() + data object CancelClick : Dialog() + data class ItemPick(val bundle: Bundle? = null) : Dialog() + } +} diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsItem.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsItem.kt index b50f9307..f15f79ad 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsItem.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsItem.kt @@ -1,13 +1,13 @@ package dev.meloda.fast.settings.model import android.content.res.Resources -import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import dev.meloda.fast.common.model.UiText import dev.meloda.fast.common.model.parseString import dev.meloda.fast.datastore.AppSettings import kotlin.reflect.KClass -@Immutable +@Stable sealed class SettingsItem( val key: String, value: T, diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsNavigationIntent.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsNavigationIntent.kt new file mode 100644 index 00000000..971f5a10 --- /dev/null +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsNavigationIntent.kt @@ -0,0 +1,8 @@ +package dev.meloda.fast.settings.model + +sealed class SettingsNavigationIntent { + data object Back : SettingsNavigationIntent() + data object Language : SettingsNavigationIntent() + data object Restart : SettingsNavigationIntent() + data object LogOut : SettingsNavigationIntent() +} diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsScreenState.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsScreenState.kt index 8028dfd7..0e661f36 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsScreenState.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/model/SettingsScreenState.kt @@ -6,13 +6,13 @@ import dev.meloda.fast.datastore.AppSettings @Immutable data class SettingsScreenState( val settings: List, - val showDebugOptions: Boolean + val dialog: SettingsDialog? ) { companion object { val EMPTY: SettingsScreenState = SettingsScreenState( settings = emptyList(), - showDebugOptions = AppSettings.Debug.showDebugCategory + dialog = null ) } } 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 9365d415..1f374de5 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 @@ -1,26 +1,37 @@ package dev.meloda.fast.settings.navigation +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import dev.meloda.fast.settings.SettingsViewModel +import dev.meloda.fast.settings.model.SettingsNavigationIntent import dev.meloda.fast.settings.presentation.SettingsRoute import kotlinx.serialization.Serializable +import org.koin.androidx.compose.koinViewModel @Serializable object Settings fun NavGraphBuilder.settingsScreen( - onBack: () -> Unit, - onLogOutButtonClicked: () -> Unit, - onLanguageItemClicked: () -> Unit, - onRestartRequired: () -> Unit, + handleNavigationIntent: (SettingsNavigationIntent) -> Unit ) { composable { + 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) + } + SettingsRoute( - onBack = onBack, - onLogOutButtonClicked = onLogOutButtonClicked, - onLanguageItemClicked = onLanguageItemClicked, - onRestartRequired = onRestartRequired + handleIntent = viewModel::handleIntent, + screenState = screenState, + hapticType = hapticType ) } } diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsDialogs.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsDialogs.kt index 4cb2858c..5c4e9a14 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsDialogs.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsDialogs.kt @@ -3,7 +3,6 @@ package dev.meloda.fast.settings.presentation import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.os.Bundle import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -26,20 +25,18 @@ import androidx.core.os.bundleOf import dev.meloda.fast.data.UserConfig import dev.meloda.fast.datastore.SettingsKeys import dev.meloda.fast.settings.model.SettingsDialog +import dev.meloda.fast.settings.model.SettingsIntent import dev.meloda.fast.settings.model.SettingsScreenState +import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.ActionInvokeDismiss import dev.meloda.fast.ui.components.MaterialDialog -import dev.meloda.fast.ui.R @Composable fun HandleDialogs( + handleIntent: (SettingsIntent.Dialog) -> Unit, screenState: SettingsScreenState, - dialog: SettingsDialog?, - onConfirmed: (SettingsDialog, Bundle) -> Unit = { _, _ -> }, - onDismissed: (SettingsDialog) -> Unit = {}, - onItemPicked: (SettingsDialog, Bundle) -> Unit = { _, _ -> } ) { - if (dialog == null) return + val dialog = screenState.dialog ?: return val context = LocalContext.current @@ -48,13 +45,13 @@ fun HandleDialogs( val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY MaterialDialog( - onDismissRequest = { onDismissed(dialog) }, + onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) }, title = stringResource( id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry else R.string.sign_out_confirm_title ), text = stringResource(id = R.string.sign_out_confirm), - confirmAction = { onConfirmed(dialog, bundleOf()) }, + confirmAction = { handleIntent(SettingsIntent.Dialog.ConfirmClick()) }, confirmText = stringResource( id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry else R.string.action_sign_out @@ -66,10 +63,10 @@ fun HandleDialogs( is SettingsDialog.PerformCrash -> { MaterialDialog( - onDismissRequest = { onDismissed(dialog) }, + onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) }, title = "Perform crash", text = "App will be crashed. Are you sure?", - confirmAction = { onConfirmed(dialog, bundleOf()) }, + confirmAction = { handleIntent(SettingsIntent.Dialog.ConfirmClick()) }, confirmText = stringResource(id = R.string.yes), cancelText = stringResource(id = R.string.cancel), actionInvokeDismiss = ActionInvokeDismiss.Always @@ -88,15 +85,16 @@ fun HandleDialogs( } MaterialDialog( - onDismissRequest = { onDismissed(dialog) }, + onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) }, title = "Import auth data", confirmAction = { - onConfirmed( - dialog, - bundleOf( - "ACCESS_TOKEN" to accessToken, - "EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null }, - "TRUSTED_HASH" to trustedHash.ifEmpty { null } + handleIntent( + SettingsIntent.Dialog.ConfirmClick( + bundleOf( + "ACCESS_TOKEN" to accessToken, + "EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null }, + "TRUSTED_HASH" to trustedHash.ifEmpty { null } + ) ) ) }, @@ -198,15 +196,16 @@ fun HandleDialogs( } MaterialDialog( - onDismissRequest = { onDismissed(dialog) }, + onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) }, title = "Export auth data", confirmAction = { - onConfirmed( - dialog, - bundleOf( - "ACCESS_TOKEN" to accessToken, - "EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null }, - "TRUSTED_HASH" to trustedHash.ifEmpty { null } + handleIntent( + SettingsIntent.Dialog.ConfirmClick( + bundleOf( + "ACCESS_TOKEN" to accessToken, + "EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null }, + "TRUSTED_HASH" to trustedHash.ifEmpty { null } + ) ) ) }, @@ -269,7 +268,8 @@ fun HandleDialogs( "Auth data copied to clipboard. Be careful with this data. If another person gets it, your account will be at risk", Toast.LENGTH_LONG ).show() - onDismissed(dialog) + + handleIntent(SettingsIntent.Dialog.Dismiss) }, modifier = Modifier.align(Alignment.CenterHorizontally) ) { 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 a66b75e0..0d2141fd 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,65 +1,24 @@ package dev.meloda.fast.settings.presentation import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import dev.meloda.fast.datastore.SettingsKeys -import dev.meloda.fast.settings.SettingsViewModel -import dev.meloda.fast.settings.model.SettingsDialog -import org.koin.compose.viewmodel.koinViewModel +import dev.meloda.fast.settings.model.HapticType +import dev.meloda.fast.settings.model.SettingsIntent +import dev.meloda.fast.settings.model.SettingsScreenState @Composable fun SettingsRoute( - onBack: () -> Unit, - onLogOutButtonClicked: () -> Unit, - onLanguageItemClicked: () -> Unit, - onRestartRequired: () -> Unit, - viewModel: SettingsViewModel = koinViewModel() + handleIntent: (SettingsIntent) -> Unit, + screenState: SettingsScreenState, + hapticType: HapticType? ) { - val screenState by viewModel.screenState.collectAsStateWithLifecycle() - val hapticType by viewModel.hapticType.collectAsStateWithLifecycle() - val dialog by viewModel.dialog.collectAsStateWithLifecycle() - val isNeedToRestart by viewModel.isNeedToRestart.collectAsStateWithLifecycle() - - LaunchedEffect(isNeedToRestart) { - if (isNeedToRestart) { - onRestartRequired() - } - } - SettingsScreen( + handleIntent = handleIntent, screenState = screenState, - hapticType = hapticType, - onBack = onBack, - onHapticPerformed = viewModel::onHapticPerformed, - onSettingsItemClicked = { key -> - when (key) { - SettingsKeys.KEY_APPEARANCE_LANGUAGE -> { - onLanguageItemClicked() - } - - else -> viewModel.onSettingsItemClicked(key) - } - }, - onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked, - onSettingsItemValueChanged = viewModel::onSettingsItemChanged + hapticType = hapticType ) HandleDialogs( + handleIntent = handleIntent, screenState = screenState, - dialog = dialog, - onConfirmed = { dialog, bundle -> - when (dialog) { - is SettingsDialog.LogOut -> { - onLogOutButtonClicked() - } - - else -> Unit - } - viewModel.onDialogConfirmed(dialog, bundle) - }, - onDismissed = viewModel::onDialogDismissed, - onItemPicked = viewModel::onDialogItemPicked ) } 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 9898cdd5..8fcacc52 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 @@ -22,7 +22,9 @@ 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 @@ -36,6 +38,7 @@ 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 import dev.meloda.fast.settings.presentation.item.ListItem @@ -43,8 +46,8 @@ import dev.meloda.fast.settings.presentation.item.SwitchItem import dev.meloda.fast.settings.presentation.item.TextFieldItem import dev.meloda.fast.settings.presentation.item.TitleItem import dev.meloda.fast.settings.presentation.item.TitleTextItem -import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.theme.LocalThemeConfig @OptIn( @@ -53,22 +56,30 @@ import dev.meloda.fast.ui.R ) @Composable fun SettingsScreen( + handleIntent: (SettingsIntent) -> Unit, screenState: SettingsScreenState = SettingsScreenState.EMPTY, - hapticType: HapticType? = null, - onBack: () -> Unit = {}, - onHapticPerformed: () -> Unit = {}, - onSettingsItemClicked: (key: String) -> Unit = {}, - onSettingsItemLongClicked: (key: String) -> Unit = {}, - onSettingsItemValueChanged: (key: String, newValue: Any?) -> Unit = { _, _ -> } + hapticType: HapticType? ) { val view = LocalView.current + val onSettingsItemClicked by rememberUpdatedState { key: String -> + handleIntent(SettingsIntent.ItemClick(key)) + } + + val onSettingsItemLongClicked by rememberUpdatedState { key: String -> + handleIntent(SettingsIntent.ItemLongClick(key)) + } + + val onSettingsItemValueChanged by rememberUpdatedState { key: String, newValue: Any? -> + handleIntent(SettingsIntent.ItemValueChanged(key, newValue)) + } + LaunchedEffect(hapticType) { if (hapticType != null) { if (AppSettings.General.enableHaptic) { view.performHapticFeedback(hapticType.getHaptic()) } - onHapticPerformed() + handleIntent(SettingsIntent.ConsumePerformHaptic) } } @@ -90,7 +101,7 @@ fun SettingsScreen( ) }, navigationIcon = { - IconButton(onClick = onBack) { + IconButton(onClick = { handleIntent(SettingsIntent.BackClick) }) { Icon( painter = painterResource(id = R.drawable.ic_arrow_back_round_24), contentDescription = "Back button"