forked from melod1n/fast-messenger
refactor(settings): route settings UI through intents and navigation effects
This commit is contained in:
@@ -59,6 +59,7 @@ import dev.meloda.fast.model.api.domain.VkUser
|
|||||||
import dev.meloda.fast.navigation.Main
|
import dev.meloda.fast.navigation.Main
|
||||||
import dev.meloda.fast.navigation.mainScreen
|
import dev.meloda.fast.navigation.mainScreen
|
||||||
import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
|
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.navigateToSettings
|
||||||
import dev.meloda.fast.settings.navigation.settingsScreen
|
import dev.meloda.fast.settings.navigation.settingsScreen
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
@@ -362,18 +363,31 @@ fun RootScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
settingsScreen(
|
settingsScreen(
|
||||||
onBack = navController::navigateUp,
|
handleNavigationIntent = { intent ->
|
||||||
onLogOutButtonClicked = { navController.navigateToAuth(true) },
|
when (intent) {
|
||||||
onLanguageItemClicked = navController::navigateToLanguagePicker,
|
SettingsNavigationIntent.Back -> navController.navigateUp()
|
||||||
onRestartRequired = {
|
SettingsNavigationIntent.Language -> navController.navigateToLanguagePicker()
|
||||||
|
SettingsNavigationIntent.Restart -> {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
val intent = Intent(activity, MainActivity::class.java)
|
val intent =
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
Intent(activity, MainActivity::class.java)
|
||||||
activity.startActivity(intent)
|
intent.setFlags(
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||||
|
Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||||
|
Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
)
|
||||||
activity.finish()
|
activity.finish()
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsNavigationIntent.LogOut -> {
|
||||||
|
navController.navigateToAuth(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
languagePickerScreen(onBack = navController::navigateUp)
|
languagePickerScreen(onBack = navController::navigateUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -641,7 +641,7 @@ class LongPollUpdatesParser(
|
|||||||
).listenValue(this) { state ->
|
).listenValue(this) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = { error ->
|
||||||
logger.error(this::class, "loadMessage(): ERROR: $error")
|
logger.error(this@LongPollUpdatesParser::class, "loadMessage(): ERROR: $error")
|
||||||
continuation.resume(null)
|
continuation.resume(null)
|
||||||
},
|
},
|
||||||
success = { response ->
|
success = { response ->
|
||||||
@@ -670,7 +670,7 @@ class LongPollUpdatesParser(
|
|||||||
).listenValue(coroutineScope) { state ->
|
).listenValue(coroutineScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = { error ->
|
||||||
logger.error(this::class, "loadConvo(): ERROR: $error")
|
logger.error(this@LongPollUpdatesParser::class, "loadConvo(): ERROR: $error")
|
||||||
continuation.resume(null)
|
continuation.resume(null)
|
||||||
},
|
},
|
||||||
success = { response ->
|
success = { response ->
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import dev.meloda.fast.common.VkConstants
|
|||||||
import dev.meloda.fast.common.extensions.findWithIndex
|
import dev.meloda.fast.common.extensions.findWithIndex
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
import dev.meloda.fast.common.extensions.listenValue
|
||||||
import dev.meloda.fast.common.extensions.setValue
|
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.DarkMode
|
||||||
import dev.meloda.fast.common.model.NetworkLogLevel
|
|
||||||
import dev.meloda.fast.common.model.LongPollState
|
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.UiText
|
||||||
import dev.meloda.fast.common.model.parseString
|
import dev.meloda.fast.common.model.parseString
|
||||||
import dev.meloda.fast.data.UserConfig
|
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.datastore.UserSettings
|
||||||
import dev.meloda.fast.domain.GetCurrentAccountUseCase
|
import dev.meloda.fast.domain.GetCurrentAccountUseCase
|
||||||
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
||||||
|
import dev.meloda.fast.logger.FastLogger
|
||||||
import dev.meloda.fast.model.database.AccountEntity
|
import dev.meloda.fast.model.database.AccountEntity
|
||||||
import dev.meloda.fast.settings.model.HapticType
|
import dev.meloda.fast.settings.model.HapticType
|
||||||
import dev.meloda.fast.settings.model.SettingsDialog
|
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.SettingsItem
|
||||||
|
import dev.meloda.fast.settings.model.SettingsNavigationIntent
|
||||||
import dev.meloda.fast.settings.model.SettingsScreenState
|
import dev.meloda.fast.settings.model.SettingsScreenState
|
||||||
import dev.meloda.fast.settings.model.TextProvider
|
import dev.meloda.fast.settings.model.TextProvider
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
@@ -45,35 +49,83 @@ class SettingsViewModel(
|
|||||||
private val getCurrentAccountUseCase: GetCurrentAccountUseCase,
|
private val getCurrentAccountUseCase: GetCurrentAccountUseCase,
|
||||||
private val userSettings: UserSettings,
|
private val userSettings: UserSettings,
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val longPollController: LongPollController
|
private val longPollController: LongPollController,
|
||||||
|
private val logger: FastLogger
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
private val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
||||||
val screenState = _screenState.asStateFlow()
|
val screenStateFlow get() = screenState.asStateFlow()
|
||||||
|
|
||||||
private val _hapticType = MutableStateFlow<HapticType?>(null)
|
private val hapticType = MutableStateFlow<HapticType?>(null)
|
||||||
val hapticType = _hapticType.asStateFlow()
|
val hapticTypeFlow get() = hapticType.asStateFlow()
|
||||||
|
|
||||||
private val _dialog = MutableStateFlow<SettingsDialog?>(null)
|
private val navigationIntent = MutableStateFlow<SettingsNavigationIntent?>(null)
|
||||||
val dialog = _dialog.asStateFlow()
|
val navigationIntentFlow get() = navigationIntent.asStateFlow()
|
||||||
|
|
||||||
private val _isNeedToRestart = MutableStateFlow(false)
|
private val settings = mutableListOf<SettingsItem<*>>()
|
||||||
val isNeedToRestart = _isNeedToRestart.asStateFlow()
|
private var showDebugCategory: Boolean = userSettings.showDebugCategory.value
|
||||||
|
|
||||||
private val settings = MutableStateFlow<List<SettingsItem<*>>>(emptyList())
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
createSettings()
|
createSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDialogConfirmed(dialog: SettingsDialog, bundle: Bundle) {
|
fun handleIntent(intent: SettingsIntent) {
|
||||||
onDialogDismissed(dialog)
|
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) {
|
when (dialog) {
|
||||||
is SettingsDialog.LogOut -> onLogOutAlertPositiveClick()
|
is SettingsDialog.LogOut -> onLogOutAlertPositiveClick()
|
||||||
is SettingsDialog.PerformCrash -> onPerformCrashPositiveButtonClicked()
|
is SettingsDialog.PerformCrash -> onPerformCrashPositiveButtonClicked()
|
||||||
|
|
||||||
is SettingsDialog.ImportAuthData -> {
|
is SettingsDialog.ImportAuthData -> {
|
||||||
|
if (bundle == null) return
|
||||||
|
|
||||||
val accessToken = bundle.getString("ACCESS_TOKEN") ?: return
|
val accessToken = bundle.getString("ACCESS_TOKEN") ?: return
|
||||||
val exchangeToken = bundle.getString("EXCHANGE_TOKEN")
|
val exchangeToken = bundle.getString("EXCHANGE_TOKEN")
|
||||||
val trustedHash = bundle.getString("TRUSTED_HASH")
|
val trustedHash = bundle.getString("TRUSTED_HASH")
|
||||||
@@ -89,6 +141,10 @@ class SettingsViewModel(
|
|||||||
).listenValue(viewModelScope) { state ->
|
).listenValue(viewModelScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = { error ->
|
||||||
|
logger.error(
|
||||||
|
this@SettingsViewModel::class,
|
||||||
|
"importAuthInfo(): loadUserById(): ERROR: $error"
|
||||||
|
)
|
||||||
UserConfig.accessToken = oldToken
|
UserConfig.accessToken = oldToken
|
||||||
},
|
},
|
||||||
success = { user ->
|
success = { user ->
|
||||||
@@ -113,7 +169,7 @@ class SettingsViewModel(
|
|||||||
|
|
||||||
accountsRepository.storeAccounts(listOf(account))
|
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) {
|
when (dialog) {
|
||||||
is SettingsDialog.LogOut -> Unit
|
is SettingsDialog.LogOut -> Unit
|
||||||
is SettingsDialog.PerformCrash -> Unit
|
is SettingsDialog.PerformCrash -> Unit
|
||||||
@@ -132,10 +189,11 @@ class SettingsViewModel(
|
|||||||
is SettingsDialog.ExportAuthData -> Unit
|
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) {
|
when (dialog) {
|
||||||
is SettingsDialog.LogOut -> Unit
|
is SettingsDialog.LogOut -> Unit
|
||||||
is SettingsDialog.PerformCrash -> Unit
|
is SettingsDialog.PerformCrash -> Unit
|
||||||
@@ -144,7 +202,7 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLogOutAlertPositiveClick() {
|
private fun onLogOutAlertPositiveClick() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val tasks = listOf(
|
val tasks = listOf(
|
||||||
async {
|
async {
|
||||||
@@ -164,35 +222,37 @@ class SettingsViewModel(
|
|||||||
)
|
)
|
||||||
|
|
||||||
tasks.awaitAll()
|
tasks.awaitAll()
|
||||||
|
|
||||||
|
navigationIntent.setValue { SettingsNavigationIntent.LogOut }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPerformCrashPositiveButtonClicked() {
|
private fun onPerformCrashPositiveButtonClicked() {
|
||||||
throw Exception("Test exception")
|
throw Exception("Test exception")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSettingsItemClicked(key: String) {
|
private fun onSettingsItemClicked(key: String) {
|
||||||
when (key) {
|
when (key) {
|
||||||
SettingsKeys.KEY_ACCOUNT_LOGOUT -> {
|
SettingsKeys.KEY_ACCOUNT_LOGOUT -> {
|
||||||
_dialog.setValue { SettingsDialog.LogOut }
|
setDialog(SettingsDialog.LogOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_DEBUG_PERFORM_CRASH -> {
|
SettingsKeys.KEY_DEBUG_PERFORM_CRASH -> {
|
||||||
_dialog.setValue { SettingsDialog.PerformCrash }
|
setDialog(SettingsDialog.PerformCrash)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_DEBUG_IMPORT_AUTH_DATA -> {
|
SettingsKeys.KEY_DEBUG_IMPORT_AUTH_DATA -> {
|
||||||
_dialog.setValue { SettingsDialog.ImportAuthData }
|
setDialog(SettingsDialog.ImportAuthData)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_DEBUG_EXPORT_AUTH_DATA -> {
|
SettingsKeys.KEY_DEBUG_EXPORT_AUTH_DATA -> {
|
||||||
_dialog.setValue {
|
setDialog(
|
||||||
SettingsDialog.ExportAuthData(
|
SettingsDialog.ExportAuthData(
|
||||||
accessToken = UserConfig.accessToken,
|
accessToken = UserConfig.accessToken,
|
||||||
exchangeToken = UserConfig.exchangeToken,
|
exchangeToken = UserConfig.exchangeToken,
|
||||||
trustedHash = UserConfig.trustedHash
|
trustedHash = UserConfig.trustedHash
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST -> {
|
SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST -> {
|
||||||
@@ -203,13 +263,13 @@ class SettingsViewModel(
|
|||||||
|
|
||||||
createSettings()
|
createSettings()
|
||||||
|
|
||||||
_hapticType.update { HapticType.REJECT }
|
hapticType.update { HapticType.REJECT }
|
||||||
_screenState.setValue { old -> old.copy(showDebugOptions = false) }
|
showDebugCategory = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSettingsItemLongClicked(key: String) {
|
private fun onSettingsItemLongClicked(key: String) {
|
||||||
when (key) {
|
when (key) {
|
||||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||||
if (AppSettings.Debug.showDebugCategory) return
|
if (AppSettings.Debug.showDebugCategory) return
|
||||||
@@ -219,18 +279,18 @@ class SettingsViewModel(
|
|||||||
|
|
||||||
createSettings()
|
createSettings()
|
||||||
|
|
||||||
_hapticType.update { HapticType.LONG_PRESS }
|
hapticType.update { HapticType.LONG_PRESS }
|
||||||
_screenState.setValue { old -> old.copy(showDebugOptions = true) }
|
showDebugCategory = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSettingsItemChanged(key: String, newValue: Any?) {
|
private fun onSettingsItemChanged(key: String, newValue: Any?) {
|
||||||
settings.value.findWithIndex { it.key == key }?.let { (index, item) ->
|
settings.findWithIndex { it.key == key }?.let { (index, item) ->
|
||||||
item.updateValue(newValue)
|
item.updateValue(newValue)
|
||||||
item.updateText()
|
item.updateText()
|
||||||
|
|
||||||
_screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
settings = old.settings.toMutableList().apply {
|
settings = old.settings.toMutableList().apply {
|
||||||
this[index] = item.asPresentation(resources)
|
this[index] = item.asPresentation(resources)
|
||||||
@@ -311,10 +371,6 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onHapticPerformed() {
|
|
||||||
_hapticType.update { null }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSettings() {
|
private fun createSettings() {
|
||||||
val accountVisible = UserConfig.isLoggedIn()
|
val accountVisible = UserConfig.isLoggedIn()
|
||||||
val accountTitle = SettingsItem.Title(
|
val accountTitle = SettingsItem.Title(
|
||||||
@@ -512,7 +568,8 @@ class SettingsViewModel(
|
|||||||
values = logLevelValues.keys.toList().map(NetworkLogLevel::value)
|
values = logLevelValues.keys.toList().map(NetworkLogLevel::value)
|
||||||
).apply {
|
).apply {
|
||||||
textProvider = TextProvider { item ->
|
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")
|
UiText.Simple("Current value: $textValue")
|
||||||
}
|
}
|
||||||
@@ -602,12 +659,10 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun emitSettings(newSettings: List<SettingsItem<*>>) {
|
private fun emitSettings(newSettings: List<SettingsItem<*>>) {
|
||||||
settings.update { newSettings }
|
settings.clear()
|
||||||
|
settings.addAll(newSettings)
|
||||||
|
|
||||||
val uiSettings = newSettings.map { item ->
|
val uiSettings = newSettings.map { it.asPresentation(resources) }
|
||||||
item.asPresentation(resources)
|
screenState.setValue { old -> old.copy(settings = uiSettings) }
|
||||||
}
|
|
||||||
|
|
||||||
_screenState.setValue { old -> old.copy(settings = uiSettings) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package dev.meloda.fast.settings.model
|
package dev.meloda.fast.settings.model
|
||||||
|
|
||||||
import android.content.res.Resources
|
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.UiText
|
||||||
import dev.meloda.fast.common.model.parseString
|
import dev.meloda.fast.common.model.parseString
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Immutable
|
@Stable
|
||||||
sealed class SettingsItem<T>(
|
sealed class SettingsItem<T>(
|
||||||
val key: String,
|
val key: String,
|
||||||
value: T,
|
value: T,
|
||||||
|
|||||||
+8
@@ -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()
|
||||||
|
}
|
||||||
+2
-2
@@ -6,13 +6,13 @@ import dev.meloda.fast.datastore.AppSettings
|
|||||||
@Immutable
|
@Immutable
|
||||||
data class SettingsScreenState(
|
data class SettingsScreenState(
|
||||||
val settings: List<UiItem>,
|
val settings: List<UiItem>,
|
||||||
val showDebugOptions: Boolean
|
val dialog: SettingsDialog?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY: SettingsScreenState = SettingsScreenState(
|
val EMPTY: SettingsScreenState = SettingsScreenState(
|
||||||
settings = emptyList(),
|
settings = emptyList(),
|
||||||
showDebugOptions = AppSettings.Debug.showDebugCategory
|
dialog = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-8
@@ -1,26 +1,37 @@
|
|||||||
package dev.meloda.fast.settings.navigation
|
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.NavController
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
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 dev.meloda.fast.settings.presentation.SettingsRoute
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Settings
|
object Settings
|
||||||
|
|
||||||
fun NavGraphBuilder.settingsScreen(
|
fun NavGraphBuilder.settingsScreen(
|
||||||
onBack: () -> Unit,
|
handleNavigationIntent: (SettingsNavigationIntent) -> Unit
|
||||||
onLogOutButtonClicked: () -> Unit,
|
|
||||||
onLanguageItemClicked: () -> Unit,
|
|
||||||
onRestartRequired: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
composable<Settings> {
|
composable<Settings> {
|
||||||
|
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(
|
SettingsRoute(
|
||||||
onBack = onBack,
|
handleIntent = viewModel::handleIntent,
|
||||||
onLogOutButtonClicked = onLogOutButtonClicked,
|
screenState = screenState,
|
||||||
onLanguageItemClicked = onLanguageItemClicked,
|
hapticType = hapticType
|
||||||
onRestartRequired = onRestartRequired
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-18
@@ -3,7 +3,6 @@ package dev.meloda.fast.settings.presentation
|
|||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
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.data.UserConfig
|
||||||
import dev.meloda.fast.datastore.SettingsKeys
|
import dev.meloda.fast.datastore.SettingsKeys
|
||||||
import dev.meloda.fast.settings.model.SettingsDialog
|
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.settings.model.SettingsScreenState
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
||||||
import dev.meloda.fast.ui.components.MaterialDialog
|
import dev.meloda.fast.ui.components.MaterialDialog
|
||||||
import dev.meloda.fast.ui.R
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HandleDialogs(
|
fun HandleDialogs(
|
||||||
|
handleIntent: (SettingsIntent.Dialog) -> Unit,
|
||||||
screenState: SettingsScreenState,
|
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
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -48,13 +45,13 @@ fun HandleDialogs(
|
|||||||
val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY
|
val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY
|
||||||
|
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissRequest = { onDismissed(dialog) },
|
onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) },
|
||||||
title = stringResource(
|
title = stringResource(
|
||||||
id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry
|
id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry
|
||||||
else R.string.sign_out_confirm_title
|
else R.string.sign_out_confirm_title
|
||||||
),
|
),
|
||||||
text = stringResource(id = R.string.sign_out_confirm),
|
text = stringResource(id = R.string.sign_out_confirm),
|
||||||
confirmAction = { onConfirmed(dialog, bundleOf()) },
|
confirmAction = { handleIntent(SettingsIntent.Dialog.ConfirmClick()) },
|
||||||
confirmText = stringResource(
|
confirmText = stringResource(
|
||||||
id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry
|
id = if (isEasterEgg) R.string.easter_egg_log_out_dmitry
|
||||||
else R.string.action_sign_out
|
else R.string.action_sign_out
|
||||||
@@ -66,10 +63,10 @@ fun HandleDialogs(
|
|||||||
|
|
||||||
is SettingsDialog.PerformCrash -> {
|
is SettingsDialog.PerformCrash -> {
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissRequest = { onDismissed(dialog) },
|
onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) },
|
||||||
title = "Perform crash",
|
title = "Perform crash",
|
||||||
text = "App will be crashed. Are you sure?",
|
text = "App will be crashed. Are you sure?",
|
||||||
confirmAction = { onConfirmed(dialog, bundleOf()) },
|
confirmAction = { handleIntent(SettingsIntent.Dialog.ConfirmClick()) },
|
||||||
confirmText = stringResource(id = R.string.yes),
|
confirmText = stringResource(id = R.string.yes),
|
||||||
cancelText = stringResource(id = R.string.cancel),
|
cancelText = stringResource(id = R.string.cancel),
|
||||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
@@ -88,17 +85,18 @@ fun HandleDialogs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissRequest = { onDismissed(dialog) },
|
onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) },
|
||||||
title = "Import auth data",
|
title = "Import auth data",
|
||||||
confirmAction = {
|
confirmAction = {
|
||||||
onConfirmed(
|
handleIntent(
|
||||||
dialog,
|
SettingsIntent.Dialog.ConfirmClick(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
"ACCESS_TOKEN" to accessToken,
|
"ACCESS_TOKEN" to accessToken,
|
||||||
"EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null },
|
"EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null },
|
||||||
"TRUSTED_HASH" to trustedHash.ifEmpty { null }
|
"TRUSTED_HASH" to trustedHash.ifEmpty { null }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
confirmText = "Import",
|
confirmText = "Import",
|
||||||
cancelText = stringResource(R.string.cancel)
|
cancelText = stringResource(R.string.cancel)
|
||||||
@@ -198,17 +196,18 @@ fun HandleDialogs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissRequest = { onDismissed(dialog) },
|
onDismissRequest = { handleIntent(SettingsIntent.Dialog.Dismiss) },
|
||||||
title = "Export auth data",
|
title = "Export auth data",
|
||||||
confirmAction = {
|
confirmAction = {
|
||||||
onConfirmed(
|
handleIntent(
|
||||||
dialog,
|
SettingsIntent.Dialog.ConfirmClick(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
"ACCESS_TOKEN" to accessToken,
|
"ACCESS_TOKEN" to accessToken,
|
||||||
"EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null },
|
"EXCHANGE_TOKEN" to exchangeToken.ifEmpty { null },
|
||||||
"TRUSTED_HASH" to trustedHash.ifEmpty { null }
|
"TRUSTED_HASH" to trustedHash.ifEmpty { null }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
confirmText = stringResource(R.string.ok),
|
confirmText = stringResource(R.string.ok),
|
||||||
) {
|
) {
|
||||||
@@ -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",
|
"Auth data copied to clipboard. Be careful with this data. If another person gets it, your account will be at risk",
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
onDismissed(dialog)
|
|
||||||
|
handleIntent(SettingsIntent.Dialog.Dismiss)
|
||||||
},
|
},
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
) {
|
) {
|
||||||
|
|||||||
+9
-50
@@ -1,65 +1,24 @@
|
|||||||
package dev.meloda.fast.settings.presentation
|
package dev.meloda.fast.settings.presentation
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import dev.meloda.fast.settings.model.HapticType
|
||||||
import androidx.compose.runtime.getValue
|
import dev.meloda.fast.settings.model.SettingsIntent
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import dev.meloda.fast.settings.model.SettingsScreenState
|
||||||
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
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsRoute(
|
fun SettingsRoute(
|
||||||
onBack: () -> Unit,
|
handleIntent: (SettingsIntent) -> Unit,
|
||||||
onLogOutButtonClicked: () -> Unit,
|
screenState: SettingsScreenState,
|
||||||
onLanguageItemClicked: () -> Unit,
|
hapticType: HapticType?
|
||||||
onRestartRequired: () -> Unit,
|
|
||||||
viewModel: SettingsViewModel = koinViewModel()
|
|
||||||
) {
|
) {
|
||||||
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(
|
SettingsScreen(
|
||||||
|
handleIntent = handleIntent,
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
hapticType = hapticType,
|
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
HandleDialogs(
|
HandleDialogs(
|
||||||
|
handleIntent = handleIntent,
|
||||||
screenState = screenState,
|
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -22,7 +22,9 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -36,6 +38,7 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
|||||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.settings.model.HapticType
|
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.SettingsScreenState
|
||||||
import dev.meloda.fast.settings.model.UiItem
|
import dev.meloda.fast.settings.model.UiItem
|
||||||
import dev.meloda.fast.settings.presentation.item.ListItem
|
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.TextFieldItem
|
||||||
import dev.meloda.fast.settings.presentation.item.TitleItem
|
import dev.meloda.fast.settings.presentation.item.TitleItem
|
||||||
import dev.meloda.fast.settings.presentation.item.TitleTextItem
|
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.R
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
@@ -53,22 +56,30 @@ import dev.meloda.fast.ui.R
|
|||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
|
handleIntent: (SettingsIntent) -> Unit,
|
||||||
screenState: SettingsScreenState = SettingsScreenState.EMPTY,
|
screenState: SettingsScreenState = SettingsScreenState.EMPTY,
|
||||||
hapticType: HapticType? = null,
|
hapticType: HapticType?
|
||||||
onBack: () -> Unit = {},
|
|
||||||
onHapticPerformed: () -> Unit = {},
|
|
||||||
onSettingsItemClicked: (key: String) -> Unit = {},
|
|
||||||
onSettingsItemLongClicked: (key: String) -> Unit = {},
|
|
||||||
onSettingsItemValueChanged: (key: String, newValue: Any?) -> Unit = { _, _ -> }
|
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
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) {
|
LaunchedEffect(hapticType) {
|
||||||
if (hapticType != null) {
|
if (hapticType != null) {
|
||||||
if (AppSettings.General.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(hapticType.getHaptic())
|
view.performHapticFeedback(hapticType.getHaptic())
|
||||||
}
|
}
|
||||||
onHapticPerformed()
|
handleIntent(SettingsIntent.ConsumePerformHaptic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +101,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = { handleIntent(SettingsIntent.BackClick) }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_arrow_back_round_24),
|
painter = painterResource(id = R.drawable.ic_arrow_back_round_24),
|
||||||
contentDescription = "Back button"
|
contentDescription = "Back button"
|
||||||
|
|||||||
Reference in New Issue
Block a user