settings refactoring
This commit is contained in:
+231
-211
@@ -1,5 +1,6 @@
|
||||
package com.meloda.app.fast.settings
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||
@@ -7,6 +8,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.common.UserConfig
|
||||
import com.meloda.app.fast.common.extensions.findWithIndex
|
||||
import com.meloda.app.fast.common.extensions.isSdkAtLeast
|
||||
import com.meloda.app.fast.common.extensions.setValue
|
||||
import com.meloda.app.fast.data.db.AccountsRepository
|
||||
@@ -19,9 +21,11 @@ import com.meloda.app.fast.model.database.AccountEntity
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.settings.model.SettingsScreenState
|
||||
import com.meloda.app.fast.settings.model.SettingsShowOptions
|
||||
import com.meloda.app.fast.settings.model.TextProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import com.meloda.app.fast.ui.R as UiR
|
||||
|
||||
@@ -48,11 +52,14 @@ interface SettingsViewModel {
|
||||
|
||||
class SettingsViewModelImpl(
|
||||
private val accountsRepository: AccountsRepository,
|
||||
private val userSettings: UserSettings
|
||||
private val userSettings: UserSettings,
|
||||
private val resources: Resources
|
||||
) : SettingsViewModel, ViewModel() {
|
||||
|
||||
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
||||
|
||||
private val settings = MutableStateFlow<List<SettingsItem<*>>>(emptyList())
|
||||
|
||||
override val isLongPollBackgroundEnabled = MutableStateFlow<Boolean?>(null)
|
||||
|
||||
init {
|
||||
@@ -111,7 +118,7 @@ class SettingsViewModelImpl(
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
useHaptics = HapticType.Reject,
|
||||
useHaptics = HapticType.REJECT,
|
||||
showDebugOptions = false
|
||||
)
|
||||
}
|
||||
@@ -122,8 +129,7 @@ class SettingsViewModelImpl(
|
||||
override fun onSettingsItemLongClicked(key: String) {
|
||||
when (key) {
|
||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||
val showDebugCategory = isDebugSettingsShown()
|
||||
if (showDebugCategory) return
|
||||
if (isDebugSettingsShown()) return
|
||||
|
||||
SettingsController.put(SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, true)
|
||||
|
||||
@@ -131,7 +137,7 @@ class SettingsViewModelImpl(
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
useHaptics = HapticType.LongPress,
|
||||
useHaptics = HapticType.LONG_PRESS,
|
||||
showDebugOptions = true
|
||||
)
|
||||
}
|
||||
@@ -140,6 +146,19 @@ class SettingsViewModelImpl(
|
||||
}
|
||||
|
||||
override fun onSettingsItemChanged(key: String, newValue: Any?) {
|
||||
settings.value.findWithIndex { it.key == key }?.let { (index, item) ->
|
||||
item.updateValue(newValue)
|
||||
item.updateText()
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
settings = old.settings.toMutableList().apply {
|
||||
this[index] = item.asPresentation(resources)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (key) {
|
||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
|
||||
val isEnabled = (newValue as? Boolean) == true
|
||||
@@ -167,7 +186,6 @@ class SettingsViewModelImpl(
|
||||
userSettings.useMultiline(isUsing)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
|
||||
val isUsing = newValue as? Boolean ?: false
|
||||
userSettings.useAmoledThemeChanged(isUsing)
|
||||
@@ -196,7 +214,7 @@ class SettingsViewModelImpl(
|
||||
}
|
||||
|
||||
override fun onHapticPerformed() {
|
||||
screenState.setValue { old -> old.copy(useHaptics = HapticType.None) }
|
||||
screenState.setValue { old -> old.copy(useHaptics = null) }
|
||||
}
|
||||
|
||||
override fun onNotificationsPermissionRequested() {
|
||||
@@ -209,221 +227,223 @@ class SettingsViewModelImpl(
|
||||
}
|
||||
|
||||
private fun createSettings() {
|
||||
viewModelScope.launch {
|
||||
val accountVisible = UserConfig.isLoggedIn()
|
||||
val accountTitle = SettingsItem.Title.build(
|
||||
key = SettingsKeys.KEY_ACCOUNT,
|
||||
title = UiText.Resource(UiR.string.settings_account_title)
|
||||
) {
|
||||
isVisible = accountVisible
|
||||
}
|
||||
val accountLogOut = SettingsItem.TitleSummary.build(
|
||||
key = SettingsKeys.KEY_ACCOUNT_LOGOUT,
|
||||
title = UiText.Resource(UiR.string.settings_account_logout_title),
|
||||
summary = UiText.Resource(UiR.string.settings_account_logout_summary)
|
||||
) {
|
||||
isVisible = accountVisible
|
||||
}
|
||||
val accountVisible = UserConfig.isLoggedIn()
|
||||
val accountTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_ACCOUNT,
|
||||
title = UiText.Resource(UiR.string.settings_account_title),
|
||||
isVisible = accountVisible
|
||||
)
|
||||
val accountLogOut = SettingsItem.TitleText(
|
||||
key = SettingsKeys.KEY_ACCOUNT_LOGOUT,
|
||||
title = UiText.Resource(UiR.string.settings_account_logout_title),
|
||||
text = UiText.Resource(UiR.string.settings_account_logout_summary),
|
||||
isVisible = accountVisible
|
||||
)
|
||||
|
||||
val generalTitle = SettingsItem.Title.build(
|
||||
key = SettingsKeys.KEY_GENERAL,
|
||||
title = UiText.Resource(UiR.string.settings_general_title)
|
||||
)
|
||||
val generalUseContactNames = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_USE_CONTACT_NAMES,
|
||||
title = UiText.Resource(UiR.string.settings_general_contact_names_title),
|
||||
summary = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||
)
|
||||
val generalTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_GENERAL,
|
||||
title = UiText.Resource(UiR.string.settings_general_title)
|
||||
)
|
||||
val generalUseContactNames = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_USE_CONTACT_NAMES,
|
||||
title = UiText.Resource(UiR.string.settings_general_contact_names_title),
|
||||
text = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||
)
|
||||
|
||||
val appearanceTitle = SettingsItem.Title.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_title)
|
||||
)
|
||||
val appearanceMultiline = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE_MULTILINE,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_MULTILINE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_multiline_title),
|
||||
summary = UiText.Resource(UiR.string.settings_appearance_multiline_summary)
|
||||
)
|
||||
val appearanceTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_APPEARANCE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_title)
|
||||
)
|
||||
val appearanceMultiline = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_MULTILINE,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_MULTILINE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_multiline_title),
|
||||
text = UiText.Resource(UiR.string.settings_appearance_multiline_summary)
|
||||
)
|
||||
|
||||
val darkThemeValues = listOf(
|
||||
AppCompatDelegate.MODE_NIGHT_YES to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM to UiText.Resource(UiR.string.settings_dark_theme_value_follow_system),
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY to UiText.Resource(UiR.string.settings_dark_theme_value_battery_saver),
|
||||
AppCompatDelegate.MODE_NIGHT_NO to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
|
||||
).toMap()
|
||||
val darkThemeValues = listOf(
|
||||
AppCompatDelegate.MODE_NIGHT_YES to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM to UiText.Resource(UiR.string.settings_dark_theme_value_follow_system),
|
||||
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY to UiText.Resource(UiR.string.settings_dark_theme_value_battery_saver),
|
||||
AppCompatDelegate.MODE_NIGHT_NO to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
|
||||
).toMap()
|
||||
|
||||
val appearanceDarkTheme = SettingsItem.ListItem.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE_DARK_THEME,
|
||||
title = UiText.Resource(UiR.string.settings_dark_theme),
|
||||
values = darkThemeValues.keys.toList(),
|
||||
valueTitles = darkThemeValues.values.toList(),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME
|
||||
) {
|
||||
summaryProvider = SettingsItem.SummaryProvider { item ->
|
||||
val darkThemeValue = darkThemeValues[item.value ?: 0]
|
||||
val appearanceDarkTheme = SettingsItem.ListItem(
|
||||
key = SettingsKeys.KEY_APPEARANCE_DARK_THEME,
|
||||
title = UiText.Resource(UiR.string.settings_dark_theme),
|
||||
valueClass = Int::class,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME,
|
||||
titles = darkThemeValues.values.toList(),
|
||||
values = darkThemeValues.keys.toList()
|
||||
).apply {
|
||||
textProvider = TextProvider { item ->
|
||||
val darkThemeValue = darkThemeValues[item.value]
|
||||
|
||||
UiText.ResourceParams(
|
||||
value = UiR.string.settings_dark_theme_current_value,
|
||||
args = listOf(
|
||||
darkThemeValue
|
||||
?: UiText.Resource(UiR.string.settings_dark_theme_current_value_unknown)
|
||||
)
|
||||
UiText.ResourceParams(
|
||||
value = UiR.string.settings_dark_theme_current_value,
|
||||
args = listOf(
|
||||
darkThemeValue
|
||||
?: UiText.Resource(UiR.string.settings_dark_theme_current_value_unknown)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
val appearanceUseAmoledDarkTheme = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE_AMOLED_THEME,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME,
|
||||
title = UiText.Resource(UiR.string.settings_amoled_dark_theme),
|
||||
summary = UiText.Resource(UiR.string.settings_amoled_dark_theme_description)
|
||||
)
|
||||
val appearanceUseDynamicColors = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_USE_DYNAMIC_COLORS,
|
||||
title = UiText.Resource(UiR.string.settings_dynamic_colors),
|
||||
isVisible = isSdkAtLeast(Build.VERSION_CODES.S),
|
||||
summary = UiText.Resource(UiR.string.settings_dynamic_colors_description),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
|
||||
)
|
||||
|
||||
val appearanceLanguage = SettingsItem.TitleSummary.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
||||
title = UiText.Resource(UiR.string.settings_application_language),
|
||||
)
|
||||
|
||||
val featuresTitle = SettingsItem.Title.build(
|
||||
key = "features",
|
||||
title = UiText.Resource(UiR.string.settings_features_title)
|
||||
)
|
||||
val featuresFastText = SettingsItem.TextField.build(
|
||||
key = SettingsKeys.KEY_FEATURES_FAST_TEXT,
|
||||
title = UiText.Resource(UiR.string.settings_features_fast_text_title),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
|
||||
).apply {
|
||||
summaryProvider = SettingsItem.SummaryProvider { settingsItem ->
|
||||
UiText.ResourceParams(
|
||||
UiR.string.pref_message_fast_text_summary,
|
||||
listOf(settingsItem.value?.ifEmpty { null })
|
||||
)
|
||||
}
|
||||
}
|
||||
val debugLongPollBackground = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
|
||||
summary = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
|
||||
)
|
||||
|
||||
val activityTitle = SettingsItem.Title.build(
|
||||
key = "activity",
|
||||
title = UiText.Resource(UiR.string.settings_activity_title)
|
||||
)
|
||||
val visibilitySendOnlineStatus = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS,
|
||||
title = UiText.Resource(UiR.string.settings_activity_send_online_title),
|
||||
summary = UiText.Resource(UiR.string.settings_activity_send_online_summary)
|
||||
)
|
||||
|
||||
val debugTitle = SettingsItem.Title.build(
|
||||
key = "debug",
|
||||
title = UiText.Resource(UiR.string.settings_debug_title)
|
||||
)
|
||||
val debugPerformCrash = SettingsItem.TitleSummary.build(
|
||||
key = SettingsKeys.KEY_DEBUG_PERFORM_CRASH,
|
||||
title = UiText.Simple("Perform crash"),
|
||||
summary = UiText.Simple("App will be crashed. Obviously")
|
||||
)
|
||||
val debugShowCrashAlert = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT,
|
||||
defaultValue = true,
|
||||
title = UiText.Simple("Show alert after crash"),
|
||||
summary = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
|
||||
)
|
||||
val debugUseBlur = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_APPEARANCE_BLUR,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_BLUR,
|
||||
title = UiText.Simple("[WIP] Use blur"),
|
||||
summary = UiText.Simple("Adds blur wherever possible\nOn android 11 and older will have transparency instead of blurring"),
|
||||
)
|
||||
val debugShowEmojiButton = SettingsItem.Switch.build(
|
||||
key = SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
||||
title = UiText.Simple("Show emoji button"),
|
||||
summary = UiText.Simple("Show emoji button in chat panel"),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
)
|
||||
|
||||
val debugHideDebugList = SettingsItem.TitleSummary.build(
|
||||
key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST,
|
||||
title = UiText.Simple("Hide debug list")
|
||||
)
|
||||
|
||||
val accountList = listOf(
|
||||
accountTitle,
|
||||
accountLogOut
|
||||
)
|
||||
val generalList = listOf(
|
||||
generalTitle,
|
||||
generalUseContactNames
|
||||
)
|
||||
val appearanceList = listOf(
|
||||
appearanceTitle,
|
||||
appearanceMultiline,
|
||||
appearanceDarkTheme,
|
||||
appearanceUseAmoledDarkTheme,
|
||||
appearanceUseDynamicColors,
|
||||
appearanceLanguage
|
||||
)
|
||||
val featuresList = listOf(
|
||||
featuresTitle,
|
||||
featuresFastText
|
||||
)
|
||||
val visibilityList = listOf(
|
||||
activityTitle,
|
||||
visibilitySendOnlineStatus,
|
||||
)
|
||||
val debugList = mutableListOf<SettingsItem<*>>()
|
||||
listOf(
|
||||
debugTitle,
|
||||
debugPerformCrash,
|
||||
debugShowCrashAlert,
|
||||
debugLongPollBackground,
|
||||
debugUseBlur,
|
||||
debugShowEmojiButton
|
||||
).forEach(debugList::add)
|
||||
|
||||
debugList += debugHideDebugList
|
||||
|
||||
val settingsList = mutableListOf<SettingsItem<*>>()
|
||||
listOf(
|
||||
accountList,
|
||||
generalList,
|
||||
appearanceList,
|
||||
featuresList,
|
||||
visibilityList,
|
||||
debugList,
|
||||
).forEach(settingsList::addAll)
|
||||
|
||||
if (!isDebugSettingsShown()) {
|
||||
settingsList.removeAll(debugList)
|
||||
}
|
||||
|
||||
screenState.setValue { old -> old.copy(settings = settingsList) }
|
||||
}
|
||||
val appearanceUseAmoledDarkTheme = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_AMOLED_THEME,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME,
|
||||
title = UiText.Resource(UiR.string.settings_amoled_dark_theme),
|
||||
text = UiText.Resource(UiR.string.settings_amoled_dark_theme_description)
|
||||
)
|
||||
val appearanceUseDynamicColors = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_USE_DYNAMIC_COLORS,
|
||||
title = UiText.Resource(UiR.string.settings_dynamic_colors),
|
||||
isVisible = isSdkAtLeast(Build.VERSION_CODES.S),
|
||||
text = UiText.Resource(UiR.string.settings_dynamic_colors_description),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
|
||||
)
|
||||
|
||||
val appearanceLanguage = SettingsItem.TitleText(
|
||||
key = SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
||||
title = UiText.Resource(UiR.string.settings_application_language),
|
||||
)
|
||||
|
||||
val featuresTitle = SettingsItem.Title(
|
||||
key = "features",
|
||||
title = UiText.Resource(UiR.string.settings_features_title)
|
||||
)
|
||||
val featuresFastText = SettingsItem.TextField(
|
||||
key = SettingsKeys.KEY_FEATURES_FAST_TEXT,
|
||||
title = UiText.Resource(UiR.string.settings_features_fast_text_title),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
|
||||
).apply {
|
||||
textProvider = TextProvider { settingsItem ->
|
||||
UiText.ResourceParams(
|
||||
UiR.string.pref_message_fast_text_summary,
|
||||
listOf(settingsItem.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
val debugLongPollBackground = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
|
||||
text = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
|
||||
)
|
||||
|
||||
val activityTitle = SettingsItem.Title(
|
||||
key = "activity",
|
||||
title = UiText.Resource(UiR.string.settings_activity_title)
|
||||
)
|
||||
val visibilitySendOnlineStatus = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS,
|
||||
title = UiText.Resource(UiR.string.settings_activity_send_online_title),
|
||||
text = UiText.Resource(UiR.string.settings_activity_send_online_summary)
|
||||
)
|
||||
|
||||
val debugTitle = SettingsItem.Title(
|
||||
key = "debug",
|
||||
title = UiText.Resource(UiR.string.settings_debug_title)
|
||||
)
|
||||
val debugPerformCrash = SettingsItem.TitleText(
|
||||
key = SettingsKeys.KEY_DEBUG_PERFORM_CRASH,
|
||||
title = UiText.Simple("Perform crash"),
|
||||
text = UiText.Simple("App will be crashed. Obviously")
|
||||
)
|
||||
val debugShowCrashAlert = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT,
|
||||
defaultValue = true,
|
||||
title = UiText.Simple("Show alert after crash"),
|
||||
text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
|
||||
)
|
||||
val debugUseBlur = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_BLUR,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_BLUR,
|
||||
title = UiText.Simple("[WIP] Use blur"),
|
||||
text = UiText.Simple("Adds blur wherever possible\nOn android 11 and older will have transparency instead of blurring"),
|
||||
)
|
||||
val debugShowEmojiButton = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
||||
title = UiText.Simple("Show emoji button"),
|
||||
text = UiText.Simple("Show emoji button in chat panel"),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
)
|
||||
|
||||
val debugHideDebugList = SettingsItem.TitleText(
|
||||
key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST,
|
||||
title = UiText.Simple("Hide debug list")
|
||||
)
|
||||
|
||||
val accountList = listOf(
|
||||
accountTitle,
|
||||
accountLogOut
|
||||
)
|
||||
val generalList = listOf(
|
||||
generalTitle,
|
||||
generalUseContactNames
|
||||
)
|
||||
val appearanceList = listOf(
|
||||
appearanceTitle,
|
||||
appearanceMultiline,
|
||||
appearanceDarkTheme,
|
||||
appearanceUseAmoledDarkTheme,
|
||||
appearanceUseDynamicColors,
|
||||
appearanceLanguage
|
||||
)
|
||||
val featuresList = listOf(
|
||||
featuresTitle,
|
||||
featuresFastText
|
||||
)
|
||||
val visibilityList = listOf(
|
||||
activityTitle,
|
||||
visibilitySendOnlineStatus,
|
||||
)
|
||||
val debugList = mutableListOf<SettingsItem<*>>()
|
||||
listOf(
|
||||
debugTitle,
|
||||
debugPerformCrash,
|
||||
debugShowCrashAlert,
|
||||
debugLongPollBackground,
|
||||
debugUseBlur,
|
||||
debugShowEmojiButton
|
||||
).forEach(debugList::add)
|
||||
|
||||
debugList += debugHideDebugList
|
||||
|
||||
val settingsList = mutableListOf<SettingsItem<*>>()
|
||||
listOf(
|
||||
accountList,
|
||||
generalList,
|
||||
appearanceList,
|
||||
featuresList,
|
||||
visibilityList,
|
||||
debugList,
|
||||
).forEach(settingsList::addAll)
|
||||
|
||||
if (!isDebugSettingsShown()) {
|
||||
settingsList.removeAll(debugList)
|
||||
}
|
||||
|
||||
emitSettings(settingsList)
|
||||
}
|
||||
|
||||
private fun emitSettings(newSettings: List<SettingsItem<*>>) {
|
||||
settings.update { newSettings }
|
||||
|
||||
val uiSettings = newSettings.map { item ->
|
||||
item.asPresentation(resources)
|
||||
}
|
||||
|
||||
screenState.setValue { old -> old.copy(settings = uiSettings) }
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface HapticType {
|
||||
data object LongPress : HapticType
|
||||
data object Reject : HapticType
|
||||
data object None : HapticType
|
||||
enum class HapticType {
|
||||
LONG_PRESS, REJECT;
|
||||
|
||||
fun getHaptic(): Int {
|
||||
return when (this) {
|
||||
LongPress -> HapticFeedbackConstantsCompat.LONG_PRESS
|
||||
Reject -> HapticFeedbackConstantsCompat.REJECT
|
||||
None -> -1
|
||||
}
|
||||
fun getHaptic(): Int = when (this) {
|
||||
LONG_PRESS -> HapticFeedbackConstantsCompat.LONG_PRESS
|
||||
REJECT -> HapticFeedbackConstantsCompat.REJECT
|
||||
}
|
||||
}
|
||||
|
||||
+206
-179
@@ -1,223 +1,250 @@
|
||||
package com.meloda.app.fast.settings.model
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.common.parseString
|
||||
import com.meloda.app.fast.datastore.SettingsController
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// TODO: 24/12/2023, Danil Nikolaev: refactor
|
||||
sealed class SettingsItem<Value>(
|
||||
open val key: String,
|
||||
@Immutable
|
||||
sealed class SettingsItem<T>(
|
||||
val key: String,
|
||||
value: T,
|
||||
defaultValue: T?
|
||||
) {
|
||||
var onTitleChanged: ((newTitle: UiText?) -> Unit)? = null
|
||||
private val haveValue
|
||||
get() = this::class !in listOf<KClass<*>>(
|
||||
Title::class,
|
||||
TitleText::class
|
||||
)
|
||||
|
||||
var title: UiText? by Delegates.observable(null) { _, _, newValue ->
|
||||
onTitleChanged?.invoke(newValue)
|
||||
init {
|
||||
require(key.trim().isNotEmpty()) {
|
||||
"Key must not be empty"
|
||||
}
|
||||
|
||||
require(!haveValue || defaultValue != null) {
|
||||
"Default value must not be null"
|
||||
}
|
||||
}
|
||||
|
||||
var onSummaryChanged: ((newSummary: UiText?) -> Unit)? = null
|
||||
var isVisible: Boolean = true
|
||||
|
||||
var summary: UiText? by Delegates.observable(null) { _, _, newValue ->
|
||||
onSummaryChanged?.invoke(newValue)
|
||||
}
|
||||
var isEnabled: Boolean = true
|
||||
|
||||
var onEnabledStateChanged: ((newEnabled: Boolean) -> Unit)? = null
|
||||
var value: T = value
|
||||
protected set(newValue) {
|
||||
field = newValue
|
||||
|
||||
var isEnabled: Boolean by Delegates.observable(true) { _, _, newValue ->
|
||||
onEnabledStateChanged?.invoke(newValue)
|
||||
}
|
||||
SettingsController.put(key, newValue)
|
||||
}
|
||||
|
||||
var onVisibleStateChanged: ((newVisible: Boolean) -> Unit)? = null
|
||||
var title: UiText? = null
|
||||
|
||||
var isVisible: Boolean by Delegates.observable(true) { _, _, newValue ->
|
||||
onVisibleStateChanged?.invoke(newValue)
|
||||
}
|
||||
var text: UiText? = null
|
||||
|
||||
var onValueChanged: ((newValue: Value?) -> Unit)? = null
|
||||
var textProvider: TextProvider<T, SettingsItem<T>>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
updateText()
|
||||
}
|
||||
|
||||
var value: Value? by Delegates.observable(null) { _, oldValue, newValue ->
|
||||
if (key.trim().isEmpty() || oldValue == newValue) return@observable
|
||||
|
||||
onValueChanged?.invoke(newValue)
|
||||
|
||||
SettingsController.put(key, newValue)
|
||||
fun updateText() {
|
||||
textProvider?.provideText(this)?.let { newText -> text = newText }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
protected fun <T> getValueFromPreferences(
|
||||
fun updateValue(newValue: Any?) {
|
||||
if (!haveValue) throw IllegalStateException("This item does not have a value")
|
||||
value = newValue as T
|
||||
}
|
||||
|
||||
class Title(
|
||||
key: String,
|
||||
classToGet: Class<T>,
|
||||
defaultValue: Any?
|
||||
): T? {
|
||||
return when (classToGet) {
|
||||
String::class.java -> SettingsController.getString(key, defaultValue as? String)
|
||||
title: UiText,
|
||||
isVisible: Boolean = true
|
||||
) : SettingsItem<Unit>(
|
||||
key = key,
|
||||
value = Unit,
|
||||
defaultValue = null
|
||||
) {
|
||||
|
||||
Boolean::class.java -> {
|
||||
SettingsController.getBoolean(key, defaultValue as? Boolean == true)
|
||||
}
|
||||
|
||||
Int::class.java -> SettingsController.getInt(key, defaultValue as? Int ?: -1)
|
||||
Long::class.java -> SettingsController.getLong(key, defaultValue as? Long ?: -1)
|
||||
Float::class.java -> SettingsController.getFloat(key, defaultValue as? Float ?: -1f)
|
||||
else -> null
|
||||
}.let { value -> value as? T }
|
||||
}
|
||||
|
||||
var defaultValue: Value? = null
|
||||
|
||||
var titleProvider: TitleProvider<SettingsItem<Value>>? by Delegates.observable(null) { _, _, _ ->
|
||||
updateTitle()
|
||||
}
|
||||
|
||||
var summaryProvider: SummaryProvider<SettingsItem<Value>>? by Delegates.observable(null) { _, _, _ ->
|
||||
updateSummary()
|
||||
}
|
||||
|
||||
fun updateTitle() {
|
||||
titleProvider?.provideTitle(this)?.let { newTitle -> title = newTitle }
|
||||
}
|
||||
|
||||
fun updateSummary() {
|
||||
summaryProvider?.provideSummary(this)?.let { newSummary -> summary = newSummary }
|
||||
}
|
||||
|
||||
fun requireValue() = requireNotNull(value)
|
||||
|
||||
fun interface TitleProvider<Item : SettingsItem<*>> {
|
||||
fun provideTitle(settingsItem: Item): UiText?
|
||||
}
|
||||
|
||||
fun interface SummaryProvider<Item : SettingsItem<*>> {
|
||||
fun provideSummary(settingsItem: Item): UiText?
|
||||
}
|
||||
|
||||
data class Title(override val key: String) : SettingsItem<Nothing>(key) {
|
||||
|
||||
companion object {
|
||||
fun build(
|
||||
key: String,
|
||||
title: UiText,
|
||||
isEnabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
builder: Title.() -> Unit = {}
|
||||
): Title {
|
||||
return Title(key).apply {
|
||||
this.title = title
|
||||
this.isEnabled = isEnabled
|
||||
this.isVisible = isVisible
|
||||
}.apply(builder)
|
||||
}
|
||||
init {
|
||||
this.title = title
|
||||
this.isVisible = isVisible
|
||||
}
|
||||
}
|
||||
|
||||
data class TitleSummary(override val key: String) : SettingsItem<String>(key) {
|
||||
class TitleText(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true
|
||||
) : SettingsItem<Unit>(
|
||||
key = key,
|
||||
value = Unit,
|
||||
defaultValue = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun build(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
summary: UiText? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
builder: TitleSummary.() -> Unit = {}
|
||||
): TitleSummary {
|
||||
return TitleSummary(key).apply {
|
||||
this.title = title
|
||||
this.summary = summary
|
||||
this.isEnabled = isEnabled
|
||||
this.isVisible = isVisible
|
||||
}.apply(builder)
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
data class TextField(override val key: String) : SettingsItem<String>(key) {
|
||||
class Switch(
|
||||
key: String,
|
||||
defaultValue: Boolean,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true,
|
||||
isChecked: Boolean? = null
|
||||
) : SettingsItem<Boolean>(
|
||||
key = key,
|
||||
value = isChecked ?: getCurrentValue(key, defaultValue),
|
||||
defaultValue = defaultValue
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun build(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
summary: UiText? = null,
|
||||
defaultValue: String? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
builder: TextField.() -> Unit = {}
|
||||
): TextField {
|
||||
return TextField(key).apply {
|
||||
this.title = title
|
||||
this.summary = summary
|
||||
this.defaultValue = defaultValue
|
||||
this.isEnabled = isEnabled
|
||||
this.isVisible = isVisible
|
||||
|
||||
this.value = SettingsController.getString(key, defaultValue)
|
||||
}.apply(builder)
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
data class Switch(override val key: String) : SettingsItem<Boolean>(key) {
|
||||
class TextField(
|
||||
key: String,
|
||||
defaultValue: String,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true,
|
||||
fieldText: String? = null
|
||||
) : SettingsItem<String>(
|
||||
key = key,
|
||||
value = fieldText ?: getCurrentValue(key, defaultValue),
|
||||
defaultValue = defaultValue
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun build(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
summary: UiText? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isChecked: Boolean? = null,
|
||||
isVisible: Boolean = true,
|
||||
defaultValue: Boolean? = null,
|
||||
builder: Switch.() -> Unit = {}
|
||||
): Switch {
|
||||
return Switch(key).apply {
|
||||
this.title = title
|
||||
this.summary = summary
|
||||
this.isEnabled = isEnabled
|
||||
this.defaultValue = defaultValue
|
||||
this.isVisible = isVisible
|
||||
|
||||
this.value = defaultValue
|
||||
?.let { value -> SettingsController.getBoolean(key, value) }
|
||||
?: isChecked
|
||||
}.apply(builder)
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
data class ListItem(
|
||||
override val key: String,
|
||||
val values: List<Int>,
|
||||
val valueTitles: List<UiText>
|
||||
) : SettingsItem<Int>(key) {
|
||||
class ListItem<T : Any>(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
valueClass: KClass<T>,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true,
|
||||
selectedValue: T? = null,
|
||||
val titles: List<UiText>,
|
||||
val values: List<T>
|
||||
) : SettingsItem<T>(
|
||||
key = key,
|
||||
value = selectedValue ?: SettingsController.get(valueClass, key, defaultValue),
|
||||
defaultValue = defaultValue
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun build(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
summary: UiText? = null,
|
||||
isEnabled: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
values: List<Int>,
|
||||
valueTitles: List<UiText>,
|
||||
defaultValue: Int? = null,
|
||||
selectedIndex: Int? = null,
|
||||
builder: ListItem.() -> Unit = {}
|
||||
): ListItem {
|
||||
return ListItem(
|
||||
key = key,
|
||||
values = values,
|
||||
valueTitles = valueTitles
|
||||
).apply {
|
||||
this.title = title
|
||||
this.summary = summary
|
||||
this.isEnabled = isEnabled
|
||||
this.isVisible = isVisible
|
||||
|
||||
this.value = defaultValue
|
||||
?.let { value -> getValueFromPreferences(key, Int::class.java, value) }
|
||||
?: selectedIndex?.let { values[it] }
|
||||
}.apply(builder)
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
require(titles.isNotEmpty()) {
|
||||
"titles must not be empty"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun asPresentation(resources: Resources): UiItem = when (val item = this) {
|
||||
is Title -> {
|
||||
UiItem.Title(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources).orEmpty(),
|
||||
isVisible = item.isVisible
|
||||
)
|
||||
}
|
||||
|
||||
is TitleText -> {
|
||||
UiItem.TitleText(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled
|
||||
)
|
||||
}
|
||||
|
||||
is Switch -> {
|
||||
UiItem.Switch(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
isChecked = item.value
|
||||
)
|
||||
}
|
||||
|
||||
is TextField -> {
|
||||
UiItem.TextField(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
fieldText = item.value
|
||||
)
|
||||
}
|
||||
|
||||
is ListItem<*> -> {
|
||||
UiItem.List(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
selectedValue = item.value,
|
||||
titles = item.titles.mapNotNull { it.parseString(resources) },
|
||||
values = item.values
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> getCurrentValue(key: String, defaultValue: T): T {
|
||||
if (T::class == Nothing::class) {
|
||||
throw IllegalStateException("Items with Nothing does not have a value")
|
||||
} else {
|
||||
return SettingsController.get(key, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.meloda.app.fast.settings.model
|
||||
|
||||
fun interface OnSettingsClickListener {
|
||||
fun onClick(key: String)
|
||||
}
|
||||
|
||||
fun interface OnSettingsLongClickListener {
|
||||
fun onLongClick(key: String)
|
||||
}
|
||||
|
||||
fun interface OnSettingsChangeListener {
|
||||
fun onChange(key: String, newValue: Any?)
|
||||
}
|
||||
+3
-3
@@ -7,8 +7,8 @@ import com.meloda.app.fast.settings.HapticType
|
||||
@Immutable
|
||||
data class SettingsScreenState(
|
||||
val showOptions: SettingsShowOptions,
|
||||
val settings: List<SettingsItem<*>>,
|
||||
val useHaptics: HapticType,
|
||||
val settings: List<UiItem>,
|
||||
val useHaptics: HapticType?,
|
||||
val isNeedToRequestNotificationPermission: Boolean,
|
||||
val showDebugOptions: Boolean
|
||||
) {
|
||||
@@ -17,7 +17,7 @@ data class SettingsScreenState(
|
||||
val EMPTY: SettingsScreenState = SettingsScreenState(
|
||||
showOptions = SettingsShowOptions.EMPTY,
|
||||
settings = emptyList(),
|
||||
useHaptics = HapticType.None,
|
||||
useHaptics = null,
|
||||
isNeedToRequestNotificationPermission = false,
|
||||
showDebugOptions = isDebugSettingsShown()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.meloda.app.fast.settings.model
|
||||
|
||||
import com.meloda.app.fast.common.UiText
|
||||
|
||||
fun interface TextProvider<T, S : SettingsItem<T>> {
|
||||
fun provideText(item: S): UiText?
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.meloda.app.fast.settings.model
|
||||
|
||||
import com.meloda.app.fast.common.UiText
|
||||
|
||||
fun interface TitleProvider<T, S : SettingsItem<T>> {
|
||||
fun provideTitle(item: S): UiText?
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.meloda.app.fast.settings.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
typealias StringList = List<String>
|
||||
typealias TypeList<T> = List<T>
|
||||
|
||||
@Immutable
|
||||
sealed class UiItem(open val key: String) {
|
||||
|
||||
data class Title(
|
||||
override val key: String,
|
||||
val title: String,
|
||||
val isVisible: Boolean
|
||||
) : UiItem(key)
|
||||
|
||||
data class TitleText(
|
||||
override val key: String,
|
||||
val title: String?,
|
||||
val text: String?,
|
||||
val isVisible: Boolean,
|
||||
val isEnabled: Boolean
|
||||
) : UiItem(key)
|
||||
|
||||
data class Switch(
|
||||
override val key: String,
|
||||
val title: String?,
|
||||
val text: String?,
|
||||
val isVisible: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val isChecked: Boolean
|
||||
) : UiItem(key)
|
||||
|
||||
data class TextField(
|
||||
override val key: String,
|
||||
val title: String?,
|
||||
val text: String?,
|
||||
val isVisible: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val fieldText: String
|
||||
) : UiItem(key)
|
||||
|
||||
data class List<T>(
|
||||
override val key: String,
|
||||
val title: String?,
|
||||
val text: String?,
|
||||
val isVisible: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val selectedValue: T,
|
||||
val titles: StringList,
|
||||
val values: TypeList<T>
|
||||
) : UiItem(key)
|
||||
}
|
||||
-4
@@ -3,11 +3,7 @@ package com.meloda.app.fast.settings.navigation
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import com.meloda.app.fast.datastore.SettingsKeys
|
||||
import com.meloda.app.fast.model.BaseError
|
||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
||||
import com.meloda.app.fast.settings.presentation.SettingsRoute
|
||||
import com.meloda.app.fast.settings.presentation.SettingsScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
||||
+50
-52
@@ -38,16 +38,15 @@ import com.meloda.app.fast.common.UserConfig
|
||||
import com.meloda.app.fast.datastore.SettingsKeys
|
||||
import com.meloda.app.fast.datastore.UserSettings
|
||||
import com.meloda.app.fast.datastore.isUsingDarkMode
|
||||
import com.meloda.app.fast.settings.HapticType
|
||||
import com.meloda.app.fast.settings.SettingsViewModel
|
||||
import com.meloda.app.fast.settings.SettingsViewModelImpl
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.settings.model.SettingsScreenState
|
||||
import com.meloda.app.fast.settings.presentation.items.EditTextSettingsItem
|
||||
import com.meloda.app.fast.settings.presentation.items.ListSettingsItem
|
||||
import com.meloda.app.fast.settings.presentation.items.SwitchSettingsItem
|
||||
import com.meloda.app.fast.settings.presentation.items.TitleSettingsItem
|
||||
import com.meloda.app.fast.settings.presentation.items.TitleSummarySettingsItem
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.settings.presentation.item.ListItem
|
||||
import com.meloda.app.fast.settings.presentation.item.SwitchItem
|
||||
import com.meloda.app.fast.settings.presentation.item.TextFieldItem
|
||||
import com.meloda.app.fast.settings.presentation.item.TitleItem
|
||||
import com.meloda.app.fast.settings.presentation.item.TitleTextItem
|
||||
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
|
||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
@@ -90,6 +89,8 @@ fun SettingsRoute(
|
||||
},
|
||||
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
|
||||
onSettingsItemValueChanged = { key, newValue ->
|
||||
viewModel.onSettingsItemChanged(key, newValue)
|
||||
|
||||
when (key) {
|
||||
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
|
||||
val newMode = newValue as? Int ?: 0
|
||||
@@ -104,8 +105,6 @@ fun SettingsRoute(
|
||||
|
||||
userSettings.useDarkThemeChanged(isUsing)
|
||||
}
|
||||
|
||||
else -> viewModel.onSettingsItemChanged(key, newValue)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -139,7 +138,7 @@ fun SettingsScreen(
|
||||
val hapticType = screenState.useHaptics
|
||||
|
||||
LaunchedEffect(hapticType) {
|
||||
if (hapticType != HapticType.None) {
|
||||
if (hapticType != null) {
|
||||
view.performHapticFeedback(hapticType.getHaptic())
|
||||
onHapticPerformed()
|
||||
}
|
||||
@@ -201,60 +200,59 @@ fun SettingsScreen(
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||
}
|
||||
|
||||
items(
|
||||
items = screenState.settings,
|
||||
key = { item -> item.key },
|
||||
key = UiItem::key,
|
||||
contentType = { item ->
|
||||
when (item) {
|
||||
is SettingsItem.ListItem -> "list_item"
|
||||
is SettingsItem.Switch -> "switch"
|
||||
is SettingsItem.TextField -> "text_field"
|
||||
is SettingsItem.Title -> "title"
|
||||
is SettingsItem.TitleSummary -> "title_summary"
|
||||
is UiItem.Title -> "title"
|
||||
is UiItem.TitleText -> "title_text"
|
||||
is UiItem.Switch -> "switch"
|
||||
is UiItem.TextField -> "text_field"
|
||||
is UiItem.List<*> -> "list"
|
||||
}
|
||||
}
|
||||
) { item ->
|
||||
when (item) {
|
||||
is SettingsItem.Title -> TitleSettingsItem(
|
||||
item = item,
|
||||
isMultiline = currentTheme.multiline,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
is UiItem.Title -> {
|
||||
TitleItem(item = item)
|
||||
}
|
||||
|
||||
is SettingsItem.TitleSummary -> TitleSummarySettingsItem(
|
||||
item = item,
|
||||
isMultiline = currentTheme.multiline,
|
||||
onSettingsClickListener = onSettingsItemClicked,
|
||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
is UiItem.TitleText -> {
|
||||
TitleTextItem(
|
||||
item = item,
|
||||
onClick = onSettingsItemClicked,
|
||||
onLongClick = onSettingsItemLongClicked
|
||||
)
|
||||
}
|
||||
|
||||
is SettingsItem.Switch -> SwitchSettingsItem(
|
||||
item = item,
|
||||
isMultiline = currentTheme.multiline,
|
||||
onSettingsClickListener = onSettingsItemClicked,
|
||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
is UiItem.Switch -> {
|
||||
SwitchItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
|
||||
is SettingsItem.TextField -> EditTextSettingsItem(
|
||||
item = item,
|
||||
isMultiline = currentTheme.multiline,
|
||||
onSettingsClickListener = onSettingsItemClicked,
|
||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
is UiItem.TextField -> {
|
||||
TextFieldItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
|
||||
is SettingsItem.ListItem -> ListSettingsItem(
|
||||
item = item,
|
||||
isMultiline = currentTheme.multiline,
|
||||
onSettingsClickListener = onSettingsItemClicked,
|
||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
is UiItem.List<*> -> {
|
||||
ListItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package com.meloda.app.fast.settings.presentation.item
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.ui.R
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
|
||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||
import com.meloda.app.fast.ui.components.SelectionType
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
import com.meloda.app.fast.ui.util.ImmutableList
|
||||
import com.meloda.app.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ListItem(
|
||||
item: UiItem.List<*>,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onChanged: (newValue: Any?) -> Unit
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
var showDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 56.dp)
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = item.isEnabled,
|
||||
onClick = {
|
||||
onClick()
|
||||
showDialog = true
|
||||
},
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
LocalContentAlpha(
|
||||
alpha = if (item.isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
) {
|
||||
item.title?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (item.isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
item.text?.let { text ->
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
ListAlertDialog(
|
||||
item = item,
|
||||
onDismissAction = { showDialog = false },
|
||||
onConfirmButtonClicked = onChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ListAlertDialog(
|
||||
item: UiItem.List<*>,
|
||||
onDismissAction: () -> Unit,
|
||||
onConfirmButtonClicked: (newValue: Any?) -> Unit
|
||||
) {
|
||||
val currentValueIndex = remember {
|
||||
item.values.indexOf(item.selectedValue)
|
||||
}
|
||||
var selectedOptionIndex by rememberSaveable {
|
||||
mutableIntStateOf(currentValueIndex)
|
||||
}
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismissAction,
|
||||
title = item.title,
|
||||
items = item.titles.toImmutableList(),
|
||||
preSelectedItems = ImmutableList.of(selectedOptionIndex),
|
||||
selectionType = SelectionType.Single,
|
||||
onItemClick = { newIndex -> selectedOptionIndex = newIndex },
|
||||
confirmText = stringResource(id = R.string.ok),
|
||||
confirmAction = {
|
||||
if (currentValueIndex != selectedOptionIndex) {
|
||||
onConfirmButtonClicked(item.values[selectedOptionIndex])
|
||||
}
|
||||
},
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
)
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package com.meloda.app.fast.settings.presentation.item
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SwitchItem(
|
||||
item: UiItem.Switch,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onChanged: (isChecked: Boolean) -> Unit
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.heightIn(min = 56.dp)
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = item.isEnabled,
|
||||
onClick = {
|
||||
onClick()
|
||||
onChanged(!item.isChecked)
|
||||
},
|
||||
onLongClick = onLongClick,
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
LocalContentAlpha(
|
||||
alpha = if (item.isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
) {
|
||||
item.title?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (item.isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
item.text?.let { text ->
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Switch(
|
||||
enabled = item.isEnabled,
|
||||
checked = item.isChecked,
|
||||
onCheckedChange = null
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
+36
-56
@@ -1,4 +1,4 @@
|
||||
package com.meloda.app.fast.settings.presentation.items
|
||||
package com.meloda.app.fast.settings.presentation.item
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -20,78 +20,63 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.settings.model.OnSettingsChangeListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsLongClickListener
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.ui.R
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
|
||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
import com.meloda.app.fast.ui.R as UiR
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun EditTextSettingsItem(
|
||||
item: SettingsItem.TextField,
|
||||
isMultiline: Boolean,
|
||||
onSettingsClickListener: OnSettingsClickListener,
|
||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
||||
onSettingsChangeListener: OnSettingsChangeListener,
|
||||
modifier: Modifier
|
||||
fun TextFieldItem(
|
||||
item: UiItem.TextField,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onChanged: (fieldText: String) -> Unit
|
||||
) {
|
||||
var title by remember { mutableStateOf(item.title) }
|
||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
||||
if (!item.isVisible) return
|
||||
|
||||
var summary by remember { mutableStateOf(item.summary) }
|
||||
item.onSummaryChanged = { newSummary -> summary = newSummary }
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
var isEnabled by remember { mutableStateOf(item.isEnabled) }
|
||||
item.onEnabledStateChanged = { newEnabled -> isEnabled = newEnabled }
|
||||
|
||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
||||
|
||||
var showDialog by remember {
|
||||
var showDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
EditTextAlert(
|
||||
item = item,
|
||||
onSettingsChangeListener = { key, newValue ->
|
||||
summary = item.summaryProvider?.provideSummary(item)
|
||||
onSettingsChangeListener.onChange(key, newValue)
|
||||
},
|
||||
onDismiss = { showDialog = false }
|
||||
onAlertConfirmClicked = onChanged,
|
||||
onDismiss = { showDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
if (!isVisible) return
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 56.dp)
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = isEnabled,
|
||||
enabled = item.isEnabled,
|
||||
onClick = {
|
||||
onSettingsClickListener.onClick(item.key)
|
||||
onClick()
|
||||
showDialog = true
|
||||
},
|
||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
@@ -102,26 +87,26 @@ fun EditTextSettingsItem(
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
alpha = if (item.isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
) {
|
||||
title?.getString()?.let { title ->
|
||||
item.title?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
alpha = if (item.isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
summary?.getString()?.let { summary ->
|
||||
item.text?.let { text ->
|
||||
Text(
|
||||
text = summary,
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
@@ -132,32 +117,27 @@ fun EditTextSettingsItem(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun EditTextAlert(
|
||||
item: SettingsItem.TextField,
|
||||
onSettingsChangeListener: OnSettingsChangeListener,
|
||||
item: UiItem.TextField,
|
||||
onAlertConfirmClicked: (newValue: String) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val (textFieldFocusable) = FocusRequester.createRefs()
|
||||
|
||||
var textFieldValue by remember {
|
||||
mutableStateOf(TextFieldValue(item.value.orEmpty()))
|
||||
mutableStateOf(TextFieldValue(item.fieldText))
|
||||
}
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = item.title,
|
||||
confirmText = UiText.Resource(UiR.string.ok),
|
||||
confirmText = stringResource(id = R.string.ok),
|
||||
confirmAction = {
|
||||
val newValue = textFieldValue.text.trim()
|
||||
|
||||
if (item.value != newValue) {
|
||||
item.value = newValue
|
||||
onSettingsChangeListener.onChange(item.key, newValue)
|
||||
}
|
||||
onAlertConfirmClicked(textFieldValue.text.trim())
|
||||
},
|
||||
cancelText = UiText.Resource(UiR.string.cancel),
|
||||
onDismissAction = onDismiss
|
||||
cancelText = stringResource(id = R.string.cancel),
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.meloda.app.fast.settings.presentation.item
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
|
||||
@Composable
|
||||
fun TitleItem(
|
||||
item: UiItem.Title,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
Text(
|
||||
text = item.title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = modifier
|
||||
.padding(
|
||||
top = 14.dp,
|
||||
end = 16.dp,
|
||||
start = 16.dp,
|
||||
bottom = 4.dp
|
||||
)
|
||||
.animateContentSize(),
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
+21
-36
@@ -1,4 +1,4 @@
|
||||
package com.meloda.app.fast.settings.presentation.items
|
||||
package com.meloda.app.fast.settings.presentation.item
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -14,52 +14,36 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsLongClickListener
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.settings.model.UiItem
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TitleSummarySettingsItem(
|
||||
item: SettingsItem.TitleSummary,
|
||||
isMultiline: Boolean,
|
||||
onSettingsClickListener: OnSettingsClickListener,
|
||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
||||
modifier: Modifier
|
||||
fun TitleTextItem(
|
||||
item: UiItem.TitleText,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (key: String) -> Unit = {},
|
||||
onLongClick: (key: String) -> Unit = {}
|
||||
) {
|
||||
var title by remember { mutableStateOf(item.title) }
|
||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
||||
if (!item.isVisible) return
|
||||
|
||||
var summary by remember { mutableStateOf(item.summary) }
|
||||
item.onSummaryChanged = { newSummary -> summary = newSummary }
|
||||
val currentTheme = LocalTheme.current
|
||||
|
||||
var isEnabled by remember { mutableStateOf(item.isEnabled) }
|
||||
item.onEnabledStateChanged = { newEnabled -> isEnabled = newEnabled }
|
||||
|
||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
||||
|
||||
if (!isVisible) return
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 56.dp)
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = isEnabled,
|
||||
onClick = { onSettingsClickListener.onClick(item.key) },
|
||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
||||
enabled = item.isEnabled,
|
||||
onClick = { onClick(item.key) },
|
||||
onLongClick = { onLongClick(item.key) },
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
@@ -71,26 +55,27 @@ fun TitleSummarySettingsItem(
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
alpha = if (item.isEnabled) ContentAlpha.high
|
||||
else ContentAlpha.disabled
|
||||
) {
|
||||
title?.getString()?.let { title ->
|
||||
item.title?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
alpha = if (item.isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
summary?.getString()?.let { summary ->
|
||||
item.text?.let { text ->
|
||||
Text(
|
||||
text = summary,
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
package com.meloda.app.fast.settings.presentation.items
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.common.UiText
|
||||
import com.meloda.app.fast.settings.model.OnSettingsChangeListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsLongClickListener
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.components.ItemsSelectionType
|
||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||
import com.meloda.app.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
import com.meloda.app.fast.ui.R as UiR
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ListSettingsItem(
|
||||
item: SettingsItem.ListItem,
|
||||
isMultiline: Boolean,
|
||||
onSettingsClickListener: OnSettingsClickListener,
|
||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
||||
onSettingsChangeListener: OnSettingsChangeListener,
|
||||
modifier: Modifier
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
var title by remember { mutableStateOf(item.title) }
|
||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
||||
|
||||
var summary by remember { mutableStateOf(item.summary) }
|
||||
item.onSummaryChanged = { newSummary -> summary = newSummary }
|
||||
|
||||
var isEnabled by remember { mutableStateOf(item.isEnabled) }
|
||||
item.onEnabledStateChanged = { newEnabled -> isEnabled = newEnabled }
|
||||
|
||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
||||
|
||||
if (!isVisible) return
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 56.dp)
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = isEnabled,
|
||||
onClick = {
|
||||
onSettingsClickListener.onClick(item.key)
|
||||
showDialog = true
|
||||
},
|
||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
) {
|
||||
title?.getString()?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
summary?.getString()?.let { summary ->
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
ListAlertDialog(
|
||||
onDismissAction = {
|
||||
showDialog = false
|
||||
},
|
||||
item = item,
|
||||
onSettingsChangeListener = { key, newValue ->
|
||||
onSettingsChangeListener.onChange(key, newValue)
|
||||
item.updateSummary()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ListAlertDialog(
|
||||
onDismissAction: () -> Unit,
|
||||
item: SettingsItem.ListItem,
|
||||
onSettingsChangeListener: OnSettingsChangeListener
|
||||
) {
|
||||
var selectedOption = item.value
|
||||
val checkedItem = item.values.indexOf(selectedOption)
|
||||
|
||||
MaterialDialog(
|
||||
onDismissAction = onDismissAction,
|
||||
title = item.title,
|
||||
items = item.valueTitles.toImmutableList(),
|
||||
preSelectedItems = listOf(checkedItem).toImmutableList(),
|
||||
itemsSelectionType = ItemsSelectionType.Single,
|
||||
onItemClick = { index ->
|
||||
selectedOption = item.values[index]
|
||||
},
|
||||
confirmText = UiText.Resource(UiR.string.ok),
|
||||
confirmAction = {
|
||||
if (item.value != selectedOption) {
|
||||
item.value = selectedOption
|
||||
onSettingsChangeListener.onChange(item.key, selectedOption)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
package com.meloda.app.fast.settings.presentation.items
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.OnSettingsChangeListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
||||
import com.meloda.app.fast.settings.model.OnSettingsLongClickListener
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.ui.basic.ContentAlpha
|
||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SwitchSettingsItem(
|
||||
item: SettingsItem.Switch,
|
||||
isMultiline: Boolean,
|
||||
onSettingsClickListener: OnSettingsClickListener,
|
||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
||||
onSettingsChangeListener: OnSettingsChangeListener,
|
||||
modifier: Modifier
|
||||
) {
|
||||
var isChecked by remember {
|
||||
mutableStateOf(item.value == true)
|
||||
}
|
||||
|
||||
val onCheckedChange = { newValue: Boolean ->
|
||||
isChecked = newValue
|
||||
|
||||
if (item.value != isChecked) {
|
||||
item.value = isChecked
|
||||
onSettingsChangeListener.onChange(item.key, isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
var title by remember { mutableStateOf(item.title) }
|
||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
||||
|
||||
var summary by remember { mutableStateOf(item.summary) }
|
||||
item.onSummaryChanged = { newSummary -> summary = newSummary }
|
||||
|
||||
var value by remember { mutableStateOf(item.value) }
|
||||
item.onValueChanged = { newValue ->
|
||||
value = newValue
|
||||
isChecked = newValue == true
|
||||
}
|
||||
|
||||
var isEnabled by remember { mutableStateOf(item.isEnabled) }
|
||||
item.onEnabledStateChanged = { newEnabled -> isEnabled = newEnabled }
|
||||
|
||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
||||
|
||||
if (!isVisible) return
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.heightIn(min = 56.dp)
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = isEnabled,
|
||||
onClick = {
|
||||
onSettingsClickListener.onClick(item.key)
|
||||
onCheckedChange.invoke(!isChecked)
|
||||
},
|
||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.high else ContentAlpha.disabled
|
||||
) {
|
||||
title?.getString()?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (isEnabled) ContentAlpha.medium else ContentAlpha.disabled
|
||||
) {
|
||||
summary?.getString()?.let { summary ->
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Switch(
|
||||
enabled = isEnabled,
|
||||
checked = isChecked,
|
||||
onCheckedChange = onCheckedChange
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.meloda.app.fast.settings.presentation.items
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.meloda.app.fast.settings.model.SettingsItem
|
||||
import com.meloda.app.fast.ui.util.getString
|
||||
|
||||
@Composable
|
||||
fun TitleSettingsItem(
|
||||
item: SettingsItem.Title,
|
||||
isMultiline: Boolean,
|
||||
modifier: Modifier
|
||||
) {
|
||||
var title by remember { mutableStateOf(item.title) }
|
||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
||||
|
||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
||||
|
||||
if (!isVisible) return
|
||||
|
||||
Text(
|
||||
text = title.getString().orEmpty(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = modifier
|
||||
.padding(
|
||||
top = 14.dp,
|
||||
end = 16.dp,
|
||||
start = 16.dp,
|
||||
bottom = 4.dp
|
||||
)
|
||||
.animateContentSize(),
|
||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user