settings refactoring
This commit is contained in:
@@ -3,6 +3,7 @@ package com.meloda.app.fast.datastore
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
object SettingsController {
|
object SettingsController {
|
||||||
|
|
||||||
@@ -39,6 +40,29 @@ object SettingsController {
|
|||||||
return preferences.getFloat(key, defaultValue)
|
return preferences.getFloat(key, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T : Any> get(clazz: KClass<T>, key: String, defaultValue: T): T {
|
||||||
|
return when (clazz) {
|
||||||
|
String::class -> getString(key, defaultValue as String)
|
||||||
|
Boolean::class -> getBoolean(key, defaultValue as Boolean)
|
||||||
|
Int::class -> getInt(key, defaultValue as Int)
|
||||||
|
Long::class -> getLong(key, defaultValue as Long)
|
||||||
|
Float::class -> getFloat(key, defaultValue as Float)
|
||||||
|
else -> throw IllegalStateException("Unsupported class: $clazz")
|
||||||
|
} as T
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> get(key: String, defaultValue: T): T {
|
||||||
|
return when (T::class) {
|
||||||
|
String::class -> getString(key, defaultValue as String)
|
||||||
|
Boolean::class -> getBoolean(key, defaultValue as Boolean)
|
||||||
|
Int::class -> getInt(key, defaultValue as Int)
|
||||||
|
Long::class -> getLong(key, defaultValue as Long)
|
||||||
|
Float::class -> getFloat(key, defaultValue as Float)
|
||||||
|
else -> throw IllegalStateException("Unsupported class: ${T::class}")
|
||||||
|
} as T
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> put(key: String, newValue: T?) {
|
fun <T> put(key: String, newValue: T?) {
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
when (newValue) {
|
when (newValue) {
|
||||||
@@ -52,13 +76,13 @@ object SettingsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isLongPollInBackgroundEnabled: Boolean
|
var isLongPollInBackgroundEnabled: Boolean
|
||||||
get() = getBoolean(
|
get() = get(
|
||||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
||||||
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
||||||
)
|
)
|
||||||
set(value) = put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
|
set(value) = put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
|
||||||
|
|
||||||
var deviceId: String
|
var deviceId: String
|
||||||
get() = getString("device_id", "").orEmpty()
|
get() = get("device_id", "")
|
||||||
set(value) = put("device_id", value)
|
set(value) = put("device_id", value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ fun MaterialDialog(
|
|||||||
neutralAction: (() -> Unit)? = null,
|
neutralAction: (() -> Unit)? = null,
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
text: String? = null,
|
text: String? = null,
|
||||||
itemsSelectionType: ItemsSelectionType = ItemsSelectionType.None,
|
selectionType: SelectionType = SelectionType.None,
|
||||||
items: ImmutableList<String> = ImmutableList.empty(),
|
items: ImmutableList<String> = ImmutableList.empty(),
|
||||||
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
||||||
onItemClick: ((index: Int) -> Unit)? = null,
|
onItemClick: ((index: Int) -> Unit)? = null,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
actionInvokeDismiss: ActionInvokeDismiss = ActionInvokeDismiss.IfNoAction,
|
actionInvokeDismiss: ActionInvokeDismiss = ActionInvokeDismiss.IfNoAction,
|
||||||
customContent: (ColumnScope.() -> Unit)? = null
|
customContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
var alertItems by remember {
|
var alertItems by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -135,12 +135,12 @@ fun MaterialDialog(
|
|||||||
if (alertItems.isNotEmpty()) {
|
if (alertItems.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
AlertItems(
|
AlertItems(
|
||||||
selectionType = itemsSelectionType,
|
selectionType = selectionType,
|
||||||
items = alertItems,
|
items = alertItems,
|
||||||
onItemClick = { index ->
|
onItemClick = { index ->
|
||||||
onItemClick?.invoke(index)
|
onItemClick?.invoke(index)
|
||||||
|
|
||||||
if (itemsSelectionType == ItemsSelectionType.None) {
|
if (selectionType == SelectionType.None) {
|
||||||
onDismissRequest.invoke()
|
onDismissRequest.invoke()
|
||||||
} else {
|
} else {
|
||||||
val newItems =
|
val newItems =
|
||||||
@@ -255,7 +255,7 @@ fun MaterialDialog(
|
|||||||
cancelAction: (() -> Unit)? = null,
|
cancelAction: (() -> Unit)? = null,
|
||||||
neutralText: UiText? = null,
|
neutralText: UiText? = null,
|
||||||
neutralAction: (() -> Unit)? = null,
|
neutralAction: (() -> Unit)? = null,
|
||||||
itemsSelectionType: ItemsSelectionType = ItemsSelectionType.None,
|
selectionType: SelectionType = SelectionType.None,
|
||||||
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
||||||
items: ImmutableList<UiText> = ImmutableList.empty(),
|
items: ImmutableList<UiText> = ImmutableList.empty(),
|
||||||
onItemClick: ((index: Int) -> Unit)? = null,
|
onItemClick: ((index: Int) -> Unit)? = null,
|
||||||
@@ -356,12 +356,12 @@ fun MaterialDialog(
|
|||||||
if (alertItems.isNotEmpty()) {
|
if (alertItems.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
AlertItems(
|
AlertItems(
|
||||||
selectionType = itemsSelectionType,
|
selectionType = selectionType,
|
||||||
items = alertItems,
|
items = alertItems,
|
||||||
onItemClick = { index ->
|
onItemClick = { index ->
|
||||||
onItemClick?.invoke(index)
|
onItemClick?.invoke(index)
|
||||||
|
|
||||||
if (itemsSelectionType == ItemsSelectionType.None) {
|
if (selectionType == SelectionType.None) {
|
||||||
onDismissRequest.invoke()
|
onDismissRequest.invoke()
|
||||||
} else {
|
} else {
|
||||||
val newItems =
|
val newItems =
|
||||||
@@ -457,7 +457,7 @@ fun MaterialDialog(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AlertItems(
|
fun AlertItems(
|
||||||
selectionType: ItemsSelectionType,
|
selectionType: SelectionType,
|
||||||
items: ImmutableList<DialogItem>,
|
items: ImmutableList<DialogItem>,
|
||||||
onItemClick: ((index: Int) -> Unit)? = null,
|
onItemClick: ((index: Int) -> Unit)? = null,
|
||||||
onItemCheckedChanged: ((index: Int) -> Unit)? = null
|
onItemCheckedChanged: ((index: Int) -> Unit)? = null
|
||||||
@@ -468,7 +468,7 @@ fun AlertItems(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(48.dp)
|
.height(48.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
if (selectionType == ItemsSelectionType.Multi) {
|
if (selectionType == SelectionType.Multi) {
|
||||||
onItemCheckedChanged?.invoke(index)
|
onItemCheckedChanged?.invoke(index)
|
||||||
} else {
|
} else {
|
||||||
onItemClick?.invoke(index)
|
onItemClick?.invoke(index)
|
||||||
@@ -478,7 +478,7 @@ fun AlertItems(
|
|||||||
) {
|
) {
|
||||||
// TODO: 29/12/2023, Danil Nikolaev: check onClick & onCheckedChange actions
|
// TODO: 29/12/2023, Danil Nikolaev: check onClick & onCheckedChange actions
|
||||||
when (selectionType) {
|
when (selectionType) {
|
||||||
ItemsSelectionType.Multi -> {
|
SelectionType.Multi -> {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = item.isSelected,
|
checked = item.isSelected,
|
||||||
@@ -486,7 +486,7 @@ fun AlertItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemsSelectionType.Single -> {
|
SelectionType.Single -> {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = item.isSelected,
|
selected = item.isSelected,
|
||||||
@@ -494,7 +494,7 @@ fun AlertItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemsSelectionType.None -> {
|
SelectionType.None -> {
|
||||||
Spacer(modifier = Modifier.width(26.dp))
|
Spacer(modifier = Modifier.width(26.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,8 +520,8 @@ sealed class ActionInvokeDismiss {
|
|||||||
data object Always : ActionInvokeDismiss()
|
data object Always : ActionInvokeDismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ItemsSelectionType {
|
sealed class SelectionType {
|
||||||
data object Single : ItemsSelectionType()
|
data object Single : SelectionType()
|
||||||
data object Multi : ItemsSelectionType()
|
data object Multi : SelectionType()
|
||||||
data object None : ItemsSelectionType()
|
data object None : SelectionType()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class ImmutableList<T>(val values: List<T>) : Iterable<T> {
|
|||||||
fun <T> List<T>.toImmutableList(): ImmutableList<T> = ImmutableList(this)
|
fun <T> List<T>.toImmutableList(): ImmutableList<T> = ImmutableList(this)
|
||||||
|
|
||||||
fun <T> empty(): ImmutableList<T> = ImmutableList(emptyList())
|
fun <T> empty(): ImmutableList<T> = ImmutableList(emptyList())
|
||||||
|
|
||||||
|
fun <T> of(vararg elements: T) =
|
||||||
|
if (elements.isNotEmpty()) copyOf(elements.asList()) else empty()
|
||||||
|
|
||||||
|
fun <T> of(element: T) = ImmutableList(listOf(element))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = values.listIterator()
|
override fun iterator(): Iterator<T> = values.listIterator()
|
||||||
|
|||||||
+231
-211
@@ -1,5 +1,6 @@
|
|||||||
package com.meloda.app.fast.settings
|
package com.meloda.app.fast.settings
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||||
@@ -7,6 +8,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.meloda.app.fast.common.UiText
|
import com.meloda.app.fast.common.UiText
|
||||||
import com.meloda.app.fast.common.UserConfig
|
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.isSdkAtLeast
|
||||||
import com.meloda.app.fast.common.extensions.setValue
|
import com.meloda.app.fast.common.extensions.setValue
|
||||||
import com.meloda.app.fast.data.db.AccountsRepository
|
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.SettingsItem
|
||||||
import com.meloda.app.fast.settings.model.SettingsScreenState
|
import com.meloda.app.fast.settings.model.SettingsScreenState
|
||||||
import com.meloda.app.fast.settings.model.SettingsShowOptions
|
import com.meloda.app.fast.settings.model.SettingsShowOptions
|
||||||
|
import com.meloda.app.fast.settings.model.TextProvider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.meloda.app.fast.ui.R as UiR
|
import com.meloda.app.fast.ui.R as UiR
|
||||||
|
|
||||||
@@ -48,11 +52,14 @@ interface SettingsViewModel {
|
|||||||
|
|
||||||
class SettingsViewModelImpl(
|
class SettingsViewModelImpl(
|
||||||
private val accountsRepository: AccountsRepository,
|
private val accountsRepository: AccountsRepository,
|
||||||
private val userSettings: UserSettings
|
private val userSettings: UserSettings,
|
||||||
|
private val resources: Resources
|
||||||
) : SettingsViewModel, ViewModel() {
|
) : SettingsViewModel, ViewModel() {
|
||||||
|
|
||||||
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
||||||
|
|
||||||
|
private val settings = MutableStateFlow<List<SettingsItem<*>>>(emptyList())
|
||||||
|
|
||||||
override val isLongPollBackgroundEnabled = MutableStateFlow<Boolean?>(null)
|
override val isLongPollBackgroundEnabled = MutableStateFlow<Boolean?>(null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -111,7 +118,7 @@ class SettingsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
useHaptics = HapticType.Reject,
|
useHaptics = HapticType.REJECT,
|
||||||
showDebugOptions = false
|
showDebugOptions = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -122,8 +129,7 @@ class SettingsViewModelImpl(
|
|||||||
override fun onSettingsItemLongClicked(key: String) {
|
override fun onSettingsItemLongClicked(key: String) {
|
||||||
when (key) {
|
when (key) {
|
||||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||||
val showDebugCategory = isDebugSettingsShown()
|
if (isDebugSettingsShown()) return
|
||||||
if (showDebugCategory) return
|
|
||||||
|
|
||||||
SettingsController.put(SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, true)
|
SettingsController.put(SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, true)
|
||||||
|
|
||||||
@@ -131,7 +137,7 @@ class SettingsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
useHaptics = HapticType.LongPress,
|
useHaptics = HapticType.LONG_PRESS,
|
||||||
showDebugOptions = true
|
showDebugOptions = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -140,6 +146,19 @@ class SettingsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSettingsItemChanged(key: String, newValue: Any?) {
|
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) {
|
when (key) {
|
||||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
|
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
|
||||||
val isEnabled = (newValue as? Boolean) == true
|
val isEnabled = (newValue as? Boolean) == true
|
||||||
@@ -167,7 +186,6 @@ class SettingsViewModelImpl(
|
|||||||
userSettings.useMultiline(isUsing)
|
userSettings.useMultiline(isUsing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
|
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
|
||||||
val isUsing = newValue as? Boolean ?: false
|
val isUsing = newValue as? Boolean ?: false
|
||||||
userSettings.useAmoledThemeChanged(isUsing)
|
userSettings.useAmoledThemeChanged(isUsing)
|
||||||
@@ -196,7 +214,7 @@ class SettingsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onHapticPerformed() {
|
override fun onHapticPerformed() {
|
||||||
screenState.setValue { old -> old.copy(useHaptics = HapticType.None) }
|
screenState.setValue { old -> old.copy(useHaptics = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNotificationsPermissionRequested() {
|
override fun onNotificationsPermissionRequested() {
|
||||||
@@ -209,221 +227,223 @@ class SettingsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createSettings() {
|
private fun createSettings() {
|
||||||
viewModelScope.launch {
|
val accountVisible = UserConfig.isLoggedIn()
|
||||||
val accountVisible = UserConfig.isLoggedIn()
|
val accountTitle = SettingsItem.Title(
|
||||||
val accountTitle = SettingsItem.Title.build(
|
key = SettingsKeys.KEY_ACCOUNT,
|
||||||
key = SettingsKeys.KEY_ACCOUNT,
|
title = UiText.Resource(UiR.string.settings_account_title),
|
||||||
title = UiText.Resource(UiR.string.settings_account_title)
|
isVisible = accountVisible
|
||||||
) {
|
)
|
||||||
isVisible = accountVisible
|
val accountLogOut = SettingsItem.TitleText(
|
||||||
}
|
key = SettingsKeys.KEY_ACCOUNT_LOGOUT,
|
||||||
val accountLogOut = SettingsItem.TitleSummary.build(
|
title = UiText.Resource(UiR.string.settings_account_logout_title),
|
||||||
key = SettingsKeys.KEY_ACCOUNT_LOGOUT,
|
text = UiText.Resource(UiR.string.settings_account_logout_summary),
|
||||||
title = UiText.Resource(UiR.string.settings_account_logout_title),
|
isVisible = accountVisible
|
||||||
summary = UiText.Resource(UiR.string.settings_account_logout_summary)
|
)
|
||||||
) {
|
|
||||||
isVisible = accountVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
val generalTitle = SettingsItem.Title.build(
|
val generalTitle = SettingsItem.Title(
|
||||||
key = SettingsKeys.KEY_GENERAL,
|
key = SettingsKeys.KEY_GENERAL,
|
||||||
title = UiText.Resource(UiR.string.settings_general_title)
|
title = UiText.Resource(UiR.string.settings_general_title)
|
||||||
)
|
)
|
||||||
val generalUseContactNames = SettingsItem.Switch.build(
|
val generalUseContactNames = SettingsItem.Switch(
|
||||||
key = SettingsKeys.KEY_USE_CONTACT_NAMES,
|
key = SettingsKeys.KEY_USE_CONTACT_NAMES,
|
||||||
title = UiText.Resource(UiR.string.settings_general_contact_names_title),
|
title = UiText.Resource(UiR.string.settings_general_contact_names_title),
|
||||||
summary = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
text = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||||
)
|
)
|
||||||
|
|
||||||
val appearanceTitle = SettingsItem.Title.build(
|
val appearanceTitle = SettingsItem.Title(
|
||||||
key = SettingsKeys.KEY_APPEARANCE,
|
key = SettingsKeys.KEY_APPEARANCE,
|
||||||
title = UiText.Resource(UiR.string.settings_appearance_title)
|
title = UiText.Resource(UiR.string.settings_appearance_title)
|
||||||
)
|
)
|
||||||
val appearanceMultiline = SettingsItem.Switch.build(
|
val appearanceMultiline = SettingsItem.Switch(
|
||||||
key = SettingsKeys.KEY_APPEARANCE_MULTILINE,
|
key = SettingsKeys.KEY_APPEARANCE_MULTILINE,
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_MULTILINE,
|
defaultValue = SettingsKeys.DEFAULT_VALUE_MULTILINE,
|
||||||
title = UiText.Resource(UiR.string.settings_appearance_multiline_title),
|
title = UiText.Resource(UiR.string.settings_appearance_multiline_title),
|
||||||
summary = UiText.Resource(UiR.string.settings_appearance_multiline_summary)
|
text = UiText.Resource(UiR.string.settings_appearance_multiline_summary)
|
||||||
)
|
)
|
||||||
|
|
||||||
val darkThemeValues = listOf(
|
val darkThemeValues = listOf(
|
||||||
AppCompatDelegate.MODE_NIGHT_YES to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
|
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_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_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)
|
AppCompatDelegate.MODE_NIGHT_NO to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
|
||||||
).toMap()
|
).toMap()
|
||||||
|
|
||||||
val appearanceDarkTheme = SettingsItem.ListItem.build(
|
val appearanceDarkTheme = SettingsItem.ListItem(
|
||||||
key = SettingsKeys.KEY_APPEARANCE_DARK_THEME,
|
key = SettingsKeys.KEY_APPEARANCE_DARK_THEME,
|
||||||
title = UiText.Resource(UiR.string.settings_dark_theme),
|
title = UiText.Resource(UiR.string.settings_dark_theme),
|
||||||
values = darkThemeValues.keys.toList(),
|
valueClass = Int::class,
|
||||||
valueTitles = darkThemeValues.values.toList(),
|
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME,
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME
|
titles = darkThemeValues.values.toList(),
|
||||||
) {
|
values = darkThemeValues.keys.toList()
|
||||||
summaryProvider = SettingsItem.SummaryProvider { item ->
|
).apply {
|
||||||
val darkThemeValue = darkThemeValues[item.value ?: 0]
|
textProvider = TextProvider { item ->
|
||||||
|
val darkThemeValue = darkThemeValues[item.value]
|
||||||
|
|
||||||
UiText.ResourceParams(
|
UiText.ResourceParams(
|
||||||
value = UiR.string.settings_dark_theme_current_value,
|
value = UiR.string.settings_dark_theme_current_value,
|
||||||
args = listOf(
|
args = listOf(
|
||||||
darkThemeValue
|
darkThemeValue
|
||||||
?: UiText.Resource(UiR.string.settings_dark_theme_current_value_unknown)
|
?: 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 {
|
enum class HapticType {
|
||||||
data object LongPress : HapticType
|
LONG_PRESS, REJECT;
|
||||||
data object Reject : HapticType
|
|
||||||
data object None : HapticType
|
|
||||||
|
|
||||||
fun getHaptic(): Int {
|
fun getHaptic(): Int = when (this) {
|
||||||
return when (this) {
|
LONG_PRESS -> HapticFeedbackConstantsCompat.LONG_PRESS
|
||||||
LongPress -> HapticFeedbackConstantsCompat.LONG_PRESS
|
REJECT -> HapticFeedbackConstantsCompat.REJECT
|
||||||
Reject -> HapticFeedbackConstantsCompat.REJECT
|
|
||||||
None -> -1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+206
-179
@@ -1,223 +1,250 @@
|
|||||||
package com.meloda.app.fast.settings.model
|
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.UiText
|
||||||
|
import com.meloda.app.fast.common.parseString
|
||||||
import com.meloda.app.fast.datastore.SettingsController
|
import com.meloda.app.fast.datastore.SettingsController
|
||||||
import kotlin.properties.Delegates
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
// TODO: 24/12/2023, Danil Nikolaev: refactor
|
@Immutable
|
||||||
sealed class SettingsItem<Value>(
|
sealed class SettingsItem<T>(
|
||||||
open val key: String,
|
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 ->
|
init {
|
||||||
onTitleChanged?.invoke(newValue)
|
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 ->
|
var isEnabled: Boolean = true
|
||||||
onSummaryChanged?.invoke(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
var onEnabledStateChanged: ((newEnabled: Boolean) -> Unit)? = null
|
var value: T = value
|
||||||
|
protected set(newValue) {
|
||||||
|
field = newValue
|
||||||
|
|
||||||
var isEnabled: Boolean by Delegates.observable(true) { _, _, newValue ->
|
SettingsController.put(key, newValue)
|
||||||
onEnabledStateChanged?.invoke(newValue)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var onVisibleStateChanged: ((newVisible: Boolean) -> Unit)? = null
|
var title: UiText? = null
|
||||||
|
|
||||||
var isVisible: Boolean by Delegates.observable(true) { _, _, newValue ->
|
var text: UiText? = null
|
||||||
onVisibleStateChanged?.invoke(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ->
|
fun updateText() {
|
||||||
if (key.trim().isEmpty() || oldValue == newValue) return@observable
|
textProvider?.provideText(this)?.let { newText -> text = newText }
|
||||||
|
|
||||||
onValueChanged?.invoke(newValue)
|
|
||||||
|
|
||||||
SettingsController.put(key, newValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@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,
|
key: String,
|
||||||
classToGet: Class<T>,
|
title: UiText,
|
||||||
defaultValue: Any?
|
isVisible: Boolean = true
|
||||||
): T? {
|
) : SettingsItem<Unit>(
|
||||||
return when (classToGet) {
|
key = key,
|
||||||
String::class.java -> SettingsController.getString(key, defaultValue as? String)
|
value = Unit,
|
||||||
|
defaultValue = null
|
||||||
|
) {
|
||||||
|
|
||||||
Boolean::class.java -> {
|
init {
|
||||||
SettingsController.getBoolean(key, defaultValue as? Boolean == true)
|
this.title = title
|
||||||
}
|
this.isVisible = isVisible
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
init {
|
||||||
fun build(
|
require(title != null || text != null) {
|
||||||
key: String,
|
"Either title or text must not be null"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
init {
|
||||||
fun build(
|
require(title != null || text != null) {
|
||||||
key: String,
|
"Either title or text must not be null"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
init {
|
||||||
|
require(title != null || text != null) {
|
||||||
fun build(
|
"Either title or text must not be null"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.title = title
|
||||||
|
this.text = textProvider?.provideText(this) ?: text
|
||||||
|
this.isVisible = isVisible
|
||||||
|
this.isEnabled = isEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ListItem(
|
class ListItem<T : Any>(
|
||||||
override val key: String,
|
key: String,
|
||||||
val values: List<Int>,
|
defaultValue: T,
|
||||||
val valueTitles: List<UiText>
|
valueClass: KClass<T>,
|
||||||
) : SettingsItem<Int>(key) {
|
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 {
|
init {
|
||||||
fun build(
|
require(title != null || text != null) {
|
||||||
key: String,
|
"Either title or text must not be null"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Immutable
|
||||||
data class SettingsScreenState(
|
data class SettingsScreenState(
|
||||||
val showOptions: SettingsShowOptions,
|
val showOptions: SettingsShowOptions,
|
||||||
val settings: List<SettingsItem<*>>,
|
val settings: List<UiItem>,
|
||||||
val useHaptics: HapticType,
|
val useHaptics: HapticType?,
|
||||||
val isNeedToRequestNotificationPermission: Boolean,
|
val isNeedToRequestNotificationPermission: Boolean,
|
||||||
val showDebugOptions: Boolean
|
val showDebugOptions: Boolean
|
||||||
) {
|
) {
|
||||||
@@ -17,7 +17,7 @@ data class SettingsScreenState(
|
|||||||
val EMPTY: SettingsScreenState = SettingsScreenState(
|
val EMPTY: SettingsScreenState = SettingsScreenState(
|
||||||
showOptions = SettingsShowOptions.EMPTY,
|
showOptions = SettingsShowOptions.EMPTY,
|
||||||
settings = emptyList(),
|
settings = emptyList(),
|
||||||
useHaptics = HapticType.None,
|
useHaptics = null,
|
||||||
isNeedToRequestNotificationPermission = false,
|
isNeedToRequestNotificationPermission = false,
|
||||||
showDebugOptions = isDebugSettingsShown()
|
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.NavController
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
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.SettingsRoute
|
||||||
import com.meloda.app.fast.settings.presentation.SettingsScreen
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@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.SettingsKeys
|
||||||
import com.meloda.app.fast.datastore.UserSettings
|
import com.meloda.app.fast.datastore.UserSettings
|
||||||
import com.meloda.app.fast.datastore.isUsingDarkMode
|
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.SettingsViewModel
|
||||||
import com.meloda.app.fast.settings.SettingsViewModelImpl
|
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.model.SettingsScreenState
|
||||||
import com.meloda.app.fast.settings.presentation.items.EditTextSettingsItem
|
import com.meloda.app.fast.settings.model.UiItem
|
||||||
import com.meloda.app.fast.settings.presentation.items.ListSettingsItem
|
import com.meloda.app.fast.settings.presentation.item.ListItem
|
||||||
import com.meloda.app.fast.settings.presentation.items.SwitchSettingsItem
|
import com.meloda.app.fast.settings.presentation.item.SwitchItem
|
||||||
import com.meloda.app.fast.settings.presentation.items.TitleSettingsItem
|
import com.meloda.app.fast.settings.presentation.item.TextFieldItem
|
||||||
import com.meloda.app.fast.settings.presentation.items.TitleSummarySettingsItem
|
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.ActionInvokeDismiss
|
||||||
import com.meloda.app.fast.ui.components.MaterialDialog
|
import com.meloda.app.fast.ui.components.MaterialDialog
|
||||||
import com.meloda.app.fast.ui.theme.LocalTheme
|
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||||
@@ -90,6 +89,8 @@ fun SettingsRoute(
|
|||||||
},
|
},
|
||||||
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
|
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
|
||||||
onSettingsItemValueChanged = { key, newValue ->
|
onSettingsItemValueChanged = { key, newValue ->
|
||||||
|
viewModel.onSettingsItemChanged(key, newValue)
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
|
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
|
||||||
val newMode = newValue as? Int ?: 0
|
val newMode = newValue as? Int ?: 0
|
||||||
@@ -104,8 +105,6 @@ fun SettingsRoute(
|
|||||||
|
|
||||||
userSettings.useDarkThemeChanged(isUsing)
|
userSettings.useDarkThemeChanged(isUsing)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> viewModel.onSettingsItemChanged(key, newValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -139,7 +138,7 @@ fun SettingsScreen(
|
|||||||
val hapticType = screenState.useHaptics
|
val hapticType = screenState.useHaptics
|
||||||
|
|
||||||
LaunchedEffect(hapticType) {
|
LaunchedEffect(hapticType) {
|
||||||
if (hapticType != HapticType.None) {
|
if (hapticType != null) {
|
||||||
view.performHapticFeedback(hapticType.getHaptic())
|
view.performHapticFeedback(hapticType.getHaptic())
|
||||||
onHapticPerformed()
|
onHapticPerformed()
|
||||||
}
|
}
|
||||||
@@ -201,60 +200,59 @@ fun SettingsScreen(
|
|||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
items(
|
||||||
items = screenState.settings,
|
items = screenState.settings,
|
||||||
key = { item -> item.key },
|
key = UiItem::key,
|
||||||
contentType = { item ->
|
contentType = { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is SettingsItem.ListItem -> "list_item"
|
is UiItem.Title -> "title"
|
||||||
is SettingsItem.Switch -> "switch"
|
is UiItem.TitleText -> "title_text"
|
||||||
is SettingsItem.TextField -> "text_field"
|
is UiItem.Switch -> "switch"
|
||||||
is SettingsItem.Title -> "title"
|
is UiItem.TextField -> "text_field"
|
||||||
is SettingsItem.TitleSummary -> "title_summary"
|
is UiItem.List<*> -> "list"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { item ->
|
) { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is SettingsItem.Title -> TitleSettingsItem(
|
is UiItem.Title -> {
|
||||||
item = item,
|
TitleItem(item = item)
|
||||||
isMultiline = currentTheme.multiline,
|
}
|
||||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
|
||||||
)
|
|
||||||
|
|
||||||
is SettingsItem.TitleSummary -> TitleSummarySettingsItem(
|
is UiItem.TitleText -> {
|
||||||
item = item,
|
TitleTextItem(
|
||||||
isMultiline = currentTheme.multiline,
|
item = item,
|
||||||
onSettingsClickListener = onSettingsItemClicked,
|
onClick = onSettingsItemClicked,
|
||||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
onLongClick = onSettingsItemLongClicked
|
||||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
is SettingsItem.Switch -> SwitchSettingsItem(
|
is UiItem.Switch -> {
|
||||||
item = item,
|
SwitchItem(
|
||||||
isMultiline = currentTheme.multiline,
|
item = item,
|
||||||
onSettingsClickListener = onSettingsItemClicked,
|
onClick = { onSettingsItemClicked(item.key) },
|
||||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
is SettingsItem.TextField -> EditTextSettingsItem(
|
is UiItem.TextField -> {
|
||||||
item = item,
|
TextFieldItem(
|
||||||
isMultiline = currentTheme.multiline,
|
item = item,
|
||||||
onSettingsClickListener = onSettingsItemClicked,
|
onClick = { onSettingsItemClicked(item.key) },
|
||||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
is SettingsItem.ListItem -> ListSettingsItem(
|
is UiItem.List<*> -> {
|
||||||
item = item,
|
ListItem(
|
||||||
isMultiline = currentTheme.multiline,
|
item = item,
|
||||||
onSettingsClickListener = onSettingsItemClicked,
|
onClick = { onSettingsItemClicked(item.key) },
|
||||||
onSettingsLongClickListener = onSettingsItemLongClicked,
|
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||||
onSettingsChangeListener = onSettingsItemValueChanged,
|
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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.animation.animateContentSize
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@@ -20,78 +20,63 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
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.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.meloda.app.fast.common.UiText
|
import com.meloda.app.fast.settings.model.UiItem
|
||||||
import com.meloda.app.fast.settings.model.OnSettingsChangeListener
|
import com.meloda.app.fast.ui.R
|
||||||
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.ContentAlpha
|
||||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
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.MaterialDialog
|
||||||
import com.meloda.app.fast.ui.util.getString
|
import com.meloda.app.fast.ui.theme.LocalTheme
|
||||||
import com.meloda.app.fast.ui.R as UiR
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EditTextSettingsItem(
|
fun TextFieldItem(
|
||||||
item: SettingsItem.TextField,
|
item: UiItem.TextField,
|
||||||
isMultiline: Boolean,
|
modifier: Modifier = Modifier,
|
||||||
onSettingsClickListener: OnSettingsClickListener,
|
onClick: () -> Unit,
|
||||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
onLongClick: () -> Unit,
|
||||||
onSettingsChangeListener: OnSettingsChangeListener,
|
onChanged: (fieldText: String) -> Unit
|
||||||
modifier: Modifier
|
|
||||||
) {
|
) {
|
||||||
var title by remember { mutableStateOf(item.title) }
|
if (!item.isVisible) return
|
||||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
|
||||||
|
|
||||||
var summary by remember { mutableStateOf(item.summary) }
|
val currentTheme = LocalTheme.current
|
||||||
item.onSummaryChanged = { newSummary -> summary = newSummary }
|
|
||||||
|
|
||||||
var isEnabled by remember { mutableStateOf(item.isEnabled) }
|
var showDialog by rememberSaveable {
|
||||||
item.onEnabledStateChanged = { newEnabled -> isEnabled = newEnabled }
|
|
||||||
|
|
||||||
var isVisible by remember { mutableStateOf(item.isVisible) }
|
|
||||||
item.onVisibleStateChanged = { newVisible -> isVisible = newVisible }
|
|
||||||
|
|
||||||
var showDialog by remember {
|
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
EditTextAlert(
|
EditTextAlert(
|
||||||
item = item,
|
item = item,
|
||||||
onSettingsChangeListener = { key, newValue ->
|
onAlertConfirmClicked = onChanged,
|
||||||
summary = item.summaryProvider?.provideSummary(item)
|
onDismiss = { showDialog = false },
|
||||||
onSettingsChangeListener.onChange(key, newValue)
|
|
||||||
},
|
|
||||||
onDismiss = { showDialog = false }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) return
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.heightIn(min = 56.dp)
|
.heightIn(min = 56.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
enabled = isEnabled,
|
enabled = item.isEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
onSettingsClickListener.onClick(item.key)
|
onClick()
|
||||||
showDialog = true
|
showDialog = true
|
||||||
},
|
},
|
||||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
onLongClick = onLongClick,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
@@ -102,26 +87,26 @@ fun EditTextSettingsItem(
|
|||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(14.dp))
|
Spacer(modifier = Modifier.height(14.dp))
|
||||||
LocalContentAlpha(
|
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(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalContentAlpha(
|
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(
|
||||||
text = summary,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -132,32 +117,27 @@ fun EditTextSettingsItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditTextAlert(
|
fun EditTextAlert(
|
||||||
item: SettingsItem.TextField,
|
item: UiItem.TextField,
|
||||||
onSettingsChangeListener: OnSettingsChangeListener,
|
onAlertConfirmClicked: (newValue: String) -> Unit,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val (textFieldFocusable) = FocusRequester.createRefs()
|
val (textFieldFocusable) = FocusRequester.createRefs()
|
||||||
|
|
||||||
var textFieldValue by remember {
|
var textFieldValue by remember {
|
||||||
mutableStateOf(TextFieldValue(item.value.orEmpty()))
|
mutableStateOf(TextFieldValue(item.fieldText))
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
title = item.title,
|
title = item.title,
|
||||||
confirmText = UiText.Resource(UiR.string.ok),
|
confirmText = stringResource(id = R.string.ok),
|
||||||
confirmAction = {
|
confirmAction = {
|
||||||
val newValue = textFieldValue.text.trim()
|
onAlertConfirmClicked(textFieldValue.text.trim())
|
||||||
|
|
||||||
if (item.value != newValue) {
|
|
||||||
item.value = newValue
|
|
||||||
onSettingsChangeListener.onChange(item.key, newValue)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
cancelText = UiText.Resource(UiR.string.cancel),
|
cancelText = stringResource(id = R.string.cancel),
|
||||||
onDismissAction = onDismiss
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
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.animation.animateContentSize
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@@ -14,52 +14,36 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.meloda.app.fast.settings.model.OnSettingsClickListener
|
import com.meloda.app.fast.settings.model.UiItem
|
||||||
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.ContentAlpha
|
||||||
import com.meloda.app.fast.ui.basic.LocalContentAlpha
|
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)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TitleSummarySettingsItem(
|
fun TitleTextItem(
|
||||||
item: SettingsItem.TitleSummary,
|
item: UiItem.TitleText,
|
||||||
isMultiline: Boolean,
|
modifier: Modifier = Modifier,
|
||||||
onSettingsClickListener: OnSettingsClickListener,
|
onClick: (key: String) -> Unit = {},
|
||||||
onSettingsLongClickListener: OnSettingsLongClickListener,
|
onLongClick: (key: String) -> Unit = {}
|
||||||
modifier: Modifier
|
|
||||||
) {
|
) {
|
||||||
var title by remember { mutableStateOf(item.title) }
|
if (!item.isVisible) return
|
||||||
item.onTitleChanged = { newTitle -> title = newTitle }
|
|
||||||
|
|
||||||
var summary by remember { mutableStateOf(item.summary) }
|
val currentTheme = LocalTheme.current
|
||||||
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(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.heightIn(min = 56.dp)
|
.heightIn(min = 56.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
enabled = isEnabled,
|
enabled = item.isEnabled,
|
||||||
onClick = { onSettingsClickListener.onClick(item.key) },
|
onClick = { onClick(item.key) },
|
||||||
onLongClick = { onSettingsLongClickListener.onLongClick(item.key) },
|
onLongClick = { onLongClick(item.key) },
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
@@ -71,26 +55,27 @@ fun TitleSummarySettingsItem(
|
|||||||
Spacer(modifier = Modifier.height(14.dp))
|
Spacer(modifier = Modifier.height(14.dp))
|
||||||
|
|
||||||
LocalContentAlpha(
|
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(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalContentAlpha(
|
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(
|
||||||
text = summary,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
maxLines = if (currentTheme.multiline) Int.MAX_VALUE else 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
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