update package name (even bigger one)
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
package dev.meloda.fast.settings
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dev.meloda.fast.common.LongPollController
|
||||
import dev.meloda.fast.common.UserConfig
|
||||
import dev.meloda.fast.common.extensions.findWithIndex
|
||||
import dev.meloda.fast.common.extensions.isSdkAtLeast
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.model.DarkMode
|
||||
import dev.meloda.fast.common.model.LongPollState
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
import dev.meloda.fast.data.db.AccountsRepository
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.datastore.SettingsKeys
|
||||
import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.model.database.AccountEntity
|
||||
import dev.meloda.fast.settings.model.SettingsItem
|
||||
import dev.meloda.fast.settings.model.SettingsScreenState
|
||||
import dev.meloda.fast.settings.model.SettingsShowOptions
|
||||
import dev.meloda.fast.settings.model.TextProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
interface SettingsViewModel {
|
||||
|
||||
val screenState: StateFlow<SettingsScreenState>
|
||||
val hapticType: StateFlow<HapticType?>
|
||||
|
||||
fun onLogOutAlertDismissed()
|
||||
fun onLogOutAlertPositiveClick()
|
||||
|
||||
fun onPerformCrashAlertDismissed()
|
||||
fun onPerformCrashPositiveButtonClicked()
|
||||
|
||||
fun onSettingsItemClicked(key: String)
|
||||
fun onSettingsItemLongClicked(key: String)
|
||||
fun onSettingsItemChanged(key: String, newValue: Any?)
|
||||
|
||||
fun onHapticPerformed()
|
||||
}
|
||||
|
||||
class SettingsViewModelImpl(
|
||||
private val accountsRepository: AccountsRepository,
|
||||
private val userSettings: UserSettings,
|
||||
private val resources: Resources,
|
||||
private val longPollController: LongPollController
|
||||
) : SettingsViewModel, ViewModel() {
|
||||
|
||||
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
|
||||
override val hapticType = MutableStateFlow<HapticType?>(null)
|
||||
|
||||
private val settings = MutableStateFlow<List<SettingsItem<*>>>(emptyList())
|
||||
|
||||
init {
|
||||
createSettings()
|
||||
}
|
||||
|
||||
override fun onLogOutAlertDismissed() {
|
||||
emitShowOptions { old -> old.copy(showLogOut = false) }
|
||||
}
|
||||
|
||||
override fun onLogOutAlertPositiveClick() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
accountsRepository.storeAccounts(
|
||||
listOf(
|
||||
AccountEntity(
|
||||
userId = UserConfig.userId,
|
||||
accessToken = "",
|
||||
fastToken = UserConfig.fastToken,
|
||||
trustedHash = UserConfig.trustedHash
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
UserConfig.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPerformCrashAlertDismissed() {
|
||||
emitShowOptions { old -> old.copy(showPerformCrash = false) }
|
||||
}
|
||||
|
||||
override fun onPerformCrashPositiveButtonClicked() {
|
||||
throw Exception("Test exception")
|
||||
}
|
||||
|
||||
override fun onSettingsItemClicked(key: String) {
|
||||
when (key) {
|
||||
SettingsKeys.KEY_ACCOUNT_LOGOUT -> {
|
||||
emitShowOptions { old -> old.copy(showLogOut = true) }
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_DEBUG_PERFORM_CRASH -> {
|
||||
emitShowOptions { old -> old.copy(showPerformCrash = true) }
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST -> {
|
||||
val showDebugCategory = AppSettings.Debug.showDebugCategory
|
||||
if (!showDebugCategory) return
|
||||
|
||||
onSettingsItemChanged(key, false)
|
||||
|
||||
createSettings()
|
||||
|
||||
hapticType.update { HapticType.REJECT }
|
||||
screenState.setValue { old -> old.copy(showDebugOptions = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSettingsItemLongClicked(key: String) {
|
||||
when (key) {
|
||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||
if (AppSettings.Debug.showDebugCategory) return
|
||||
|
||||
onSettingsItemChanged(key, true)
|
||||
|
||||
createSettings()
|
||||
|
||||
hapticType.update { HapticType.LONG_PRESS }
|
||||
screenState.setValue { old -> old.copy(showDebugOptions = true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSettingsItemChanged(key: String, newValue: Any?) {
|
||||
settings.value.findWithIndex { it.key == key }?.let { (index, item) ->
|
||||
item.updateValue(newValue)
|
||||
item.updateText()
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
settings = old.settings.toMutableList().apply {
|
||||
this[index] = item.asPresentation(resources)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (key) {
|
||||
SettingsKeys.KEY_USE_CONTACT_NAMES -> {
|
||||
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||
userSettings.onUseContactNamesChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH -> {
|
||||
val enable =
|
||||
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
|
||||
userSettings.onEnablePullToRefreshChanged(enable)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
|
||||
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_MULTILINE
|
||||
userSettings.onEnableMultilineChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_DARK_MODE -> {
|
||||
val newMode = newValue as? Int ?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_MODE
|
||||
AppCompatDelegate.setDefaultNightMode(newMode)
|
||||
userSettings.onDarkModeChanged(DarkMode.parse(newMode))
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
|
||||
val isUsing =
|
||||
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME
|
||||
userSettings.onEnableAmoledDarkChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_USE_DYNAMIC_COLORS -> {
|
||||
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
|
||||
userSettings.onEnableDynamicColorsChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_LANGUAGE -> {
|
||||
val newLanguage = newValue as? String ?: SettingsKeys.DEFAULT_APPEARANCE_LANGUAGE
|
||||
userSettings.onAppLanguageChanged(newLanguage)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT -> {
|
||||
val newText = newValue as? String ?: SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
|
||||
userSettings.onFastTextChanged(newText)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||
val isUsing = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS
|
||||
userSettings.onSendOnlineStatusChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT -> {
|
||||
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
userSettings.onShowAlertAfterCrashChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
|
||||
val inBackground = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
||||
userSettings.onLongPollInBackgroundChanged(inBackground)
|
||||
|
||||
longPollController.setStateToApply(
|
||||
longPollController.stateToApply.value.let { state ->
|
||||
if (state.isLaunched()) {
|
||||
if (inBackground) LongPollState.Background
|
||||
else LongPollState.InApp
|
||||
} else state
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_USE_BLUR -> {
|
||||
val isUsing =
|
||||
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR
|
||||
userSettings.onUseBlurChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_SHOW_EMOJI_BUTTON -> {
|
||||
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
userSettings.onShowEmojiButtonChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES -> {
|
||||
val show = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES
|
||||
userSettings.onShowTimeInActionMessagesChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY -> {
|
||||
val show = newValue as? Boolean ?: false
|
||||
userSettings.onShowDebugCategoryChanged(show)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHapticPerformed() {
|
||||
hapticType.update { null }
|
||||
}
|
||||
|
||||
private fun emitShowOptions(function: (SettingsShowOptions) -> SettingsShowOptions) {
|
||||
val newShowOptions = function.invoke(screenState.value.showOptions)
|
||||
screenState.setValue { old -> old.copy(showOptions = newShowOptions) }
|
||||
}
|
||||
|
||||
private fun createSettings() {
|
||||
val accountVisible = UserConfig.isLoggedIn()
|
||||
val accountTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_ACCOUNT,
|
||||
title = UiText.Resource(UiR.string.settings_account_title),
|
||||
isVisible = accountVisible
|
||||
)
|
||||
val accountLogOut = SettingsItem.TitleText(
|
||||
key = SettingsKeys.KEY_ACCOUNT_LOGOUT,
|
||||
title = UiText.Resource(UiR.string.settings_account_logout_title),
|
||||
text = UiText.Resource(UiR.string.settings_account_logout_summary),
|
||||
isVisible = accountVisible
|
||||
)
|
||||
|
||||
val generalTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_GENERAL,
|
||||
title = UiText.Resource(UiR.string.settings_general_title)
|
||||
)
|
||||
val generalUseContactNames = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_USE_CONTACT_NAMES,
|
||||
title = UiText.Resource(UiR.string.settings_general_contact_names_title),
|
||||
text = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||
)
|
||||
val generalEnablePullToRefresh = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH,
|
||||
title = UiText.Resource(UiR.string.settings_general_enable_pull_to_refresh_title)
|
||||
)
|
||||
|
||||
val appearanceTitle = SettingsItem.Title(
|
||||
key = SettingsKeys.KEY_APPEARANCE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_title)
|
||||
)
|
||||
val appearanceMultiline = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_MULTILINE,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_MULTILINE,
|
||||
title = UiText.Resource(UiR.string.settings_appearance_multiline_title),
|
||||
text = UiText.Resource(UiR.string.settings_appearance_multiline_summary)
|
||||
)
|
||||
|
||||
val darkThemeValues = listOf(
|
||||
DarkMode.ENABLED to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
|
||||
DarkMode.FOLLOW_SYSTEM to UiText.Resource(UiR.string.settings_dark_theme_value_follow_system),
|
||||
DarkMode.AUTO_BATTERY to UiText.Resource(UiR.string.settings_dark_theme_value_battery_saver),
|
||||
DarkMode.DISABLED to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
|
||||
).toMap()
|
||||
|
||||
val appearanceDarkTheme = SettingsItem.ListItem(
|
||||
key = SettingsKeys.KEY_APPEARANCE_DARK_MODE,
|
||||
title = UiText.Resource(UiR.string.settings_dark_theme),
|
||||
valueClass = Int::class,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_MODE,
|
||||
titles = darkThemeValues.values.toList(),
|
||||
values = darkThemeValues.keys.toList().map(DarkMode::value)
|
||||
).apply {
|
||||
textProvider = TextProvider { item ->
|
||||
val darkThemeValue = darkThemeValues[DarkMode.parse(item.value)]
|
||||
|
||||
UiText.ResourceParams(
|
||||
value = UiR.string.settings_dark_theme_current_value,
|
||||
args = listOf(
|
||||
darkThemeValue
|
||||
?: UiText.Resource(UiR.string.settings_dark_theme_current_value_unknown)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
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 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 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 debugUseBlur = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_USE_BLUR,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_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 debugShowTimeInActionMessages = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
|
||||
title = UiText.Simple("Show time in action messages")
|
||||
)
|
||||
|
||||
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,
|
||||
generalEnablePullToRefresh
|
||||
)
|
||||
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,
|
||||
debugShowTimeInActionMessages
|
||||
).forEach(debugList::add)
|
||||
|
||||
debugList += debugHideDebugList
|
||||
|
||||
val settingsList = mutableListOf<SettingsItem<*>>()
|
||||
listOf(
|
||||
accountList,
|
||||
generalList,
|
||||
appearanceList,
|
||||
featuresList,
|
||||
visibilityList,
|
||||
debugList,
|
||||
).forEach(settingsList::addAll)
|
||||
|
||||
if (!AppSettings.Debug.showDebugCategory) {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
enum class HapticType {
|
||||
LONG_PRESS, REJECT;
|
||||
|
||||
fun getHaptic(): Int = when (this) {
|
||||
LONG_PRESS -> HapticFeedbackConstantsCompat.LONG_PRESS
|
||||
REJECT -> HapticFeedbackConstantsCompat.REJECT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dev.meloda.fast.settings.di
|
||||
|
||||
import dev.meloda.fast.settings.SettingsViewModel
|
||||
import dev.meloda.fast.settings.SettingsViewModelImpl
|
||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
val settingsModule = module {
|
||||
viewModelOf(::SettingsViewModelImpl) bind SettingsViewModel::class
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package dev.meloda.fast.settings.model
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.runtime.Immutable
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
import dev.meloda.fast.common.model.parseString
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Immutable
|
||||
sealed class SettingsItem<T>(
|
||||
val key: String,
|
||||
value: T,
|
||||
defaultValue: T?
|
||||
) {
|
||||
private val haveValue
|
||||
get() = this::class !in listOf<KClass<*>>(
|
||||
Title::class,
|
||||
TitleText::class
|
||||
)
|
||||
|
||||
init {
|
||||
require(key.trim().isNotEmpty()) {
|
||||
"Key must not be empty"
|
||||
}
|
||||
|
||||
require(!haveValue || defaultValue != null) {
|
||||
"Default value must not be null"
|
||||
}
|
||||
}
|
||||
|
||||
var isVisible: Boolean = true
|
||||
|
||||
var isEnabled: Boolean = true
|
||||
|
||||
var value: T = value
|
||||
protected set(newValue) {
|
||||
field = newValue
|
||||
|
||||
AppSettings.put(key, newValue)
|
||||
}
|
||||
|
||||
var title: UiText? = null
|
||||
|
||||
var text: UiText? = null
|
||||
|
||||
var textProvider: TextProvider<T, SettingsItem<T>>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
updateText()
|
||||
}
|
||||
|
||||
fun updateText() {
|
||||
textProvider?.provideText(this)?.let { newText -> text = newText }
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun updateValue(newValue: Any?) {
|
||||
if (!haveValue) throw IllegalStateException("This item does not have a value")
|
||||
value = newValue as T
|
||||
}
|
||||
|
||||
class Title(
|
||||
key: String,
|
||||
title: UiText,
|
||||
isVisible: Boolean = true
|
||||
) : SettingsItem<Unit>(
|
||||
key = key,
|
||||
value = Unit,
|
||||
defaultValue = null
|
||||
) {
|
||||
|
||||
init {
|
||||
this.title = title
|
||||
this.isVisible = isVisible
|
||||
}
|
||||
}
|
||||
|
||||
class TitleText(
|
||||
key: String,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true
|
||||
) : SettingsItem<Unit>(
|
||||
key = key,
|
||||
value = Unit,
|
||||
defaultValue = null
|
||||
) {
|
||||
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
class ListItem<T : Any>(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
valueClass: KClass<T>,
|
||||
title: UiText? = null,
|
||||
text: UiText? = null,
|
||||
isVisible: Boolean = true,
|
||||
isEnabled: Boolean = true,
|
||||
selectedValue: T? = null,
|
||||
val titles: List<UiText>,
|
||||
val values: List<T>
|
||||
) : SettingsItem<T>(
|
||||
key = key,
|
||||
value = selectedValue ?: AppSettings.get(valueClass, key, defaultValue),
|
||||
defaultValue = defaultValue
|
||||
) {
|
||||
|
||||
init {
|
||||
require(title != null || text != null) {
|
||||
"Either title or text must not be null"
|
||||
}
|
||||
|
||||
require(titles.isNotEmpty()) {
|
||||
"titles must not be empty"
|
||||
}
|
||||
|
||||
this.title = title
|
||||
this.text = textProvider?.provideText(this) ?: text
|
||||
this.isVisible = isVisible
|
||||
this.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun asPresentation(resources: Resources): UiItem = when (val item = this) {
|
||||
is Title -> {
|
||||
UiItem.Title(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources).orEmpty(),
|
||||
isVisible = item.isVisible
|
||||
)
|
||||
}
|
||||
|
||||
is TitleText -> {
|
||||
UiItem.TitleText(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled
|
||||
)
|
||||
}
|
||||
|
||||
is Switch -> {
|
||||
UiItem.Switch(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
isChecked = item.value
|
||||
)
|
||||
}
|
||||
|
||||
is TextField -> {
|
||||
UiItem.TextField(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
fieldText = item.value
|
||||
)
|
||||
}
|
||||
|
||||
is ListItem<*> -> {
|
||||
UiItem.List(
|
||||
key = item.key,
|
||||
title = item.title.parseString(resources),
|
||||
text = item.text.parseString(resources),
|
||||
isVisible = item.isVisible,
|
||||
isEnabled = item.isEnabled,
|
||||
selectedValue = item.value,
|
||||
titles = item.titles.mapNotNull { it.parseString(resources) },
|
||||
values = item.values
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> getCurrentValue(key: String, defaultValue: T): T {
|
||||
if (T::class == Nothing::class) {
|
||||
throw IllegalStateException("Items with Nothing does not have a value")
|
||||
} else {
|
||||
return AppSettings.get(key, defaultValue)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package dev.meloda.fast.settings.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
|
||||
@Immutable
|
||||
data class SettingsScreenState(
|
||||
val showOptions: SettingsShowOptions,
|
||||
val settings: List<UiItem>,
|
||||
val showDebugOptions: Boolean
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY: SettingsScreenState = SettingsScreenState(
|
||||
showOptions = SettingsShowOptions.EMPTY,
|
||||
settings = emptyList(),
|
||||
showDebugOptions = AppSettings.Debug.showDebugCategory
|
||||
)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package dev.meloda.fast.settings.model
|
||||
|
||||
data class SettingsShowOptions(
|
||||
val showLogOut: Boolean,
|
||||
val showPerformCrash: Boolean,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY: SettingsShowOptions = SettingsShowOptions(
|
||||
showLogOut = false,
|
||||
showPerformCrash = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.meloda.fast.settings.model
|
||||
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
|
||||
fun interface TextProvider<T, S : SettingsItem<T>> {
|
||||
fun provideText(item: S): UiText?
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.meloda.fast.settings.model
|
||||
|
||||
import dev.meloda.fast.common.model.UiText
|
||||
|
||||
fun interface TitleProvider<T, S : SettingsItem<T>> {
|
||||
fun provideTitle(item: S): UiText?
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.meloda.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)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.meloda.fast.settings.navigation
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import dev.meloda.fast.settings.presentation.SettingsRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
object Settings
|
||||
|
||||
fun NavGraphBuilder.settingsScreen(
|
||||
onBack: () -> Unit,
|
||||
onLogOutButtonClicked: () -> Unit,
|
||||
onLanguageItemClicked: () -> Unit
|
||||
) {
|
||||
composable<Settings> {
|
||||
SettingsRoute(
|
||||
onBack = onBack,
|
||||
onLogOutButtonClicked = onLogOutButtonClicked,
|
||||
onLanguageItemClicked = onLanguageItemClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToSettings() {
|
||||
this.navigate(Settings)
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package dev.meloda.fast.settings.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import dev.meloda.fast.common.UserConfig
|
||||
import dev.meloda.fast.datastore.SettingsKeys
|
||||
import dev.meloda.fast.settings.HapticType
|
||||
import dev.meloda.fast.settings.SettingsViewModel
|
||||
import dev.meloda.fast.settings.SettingsViewModelImpl
|
||||
import dev.meloda.fast.settings.model.SettingsScreenState
|
||||
import dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.settings.presentation.item.ListItem
|
||||
import dev.meloda.fast.settings.presentation.item.SwitchItem
|
||||
import dev.meloda.fast.settings.presentation.item.TextFieldItem
|
||||
import dev.meloda.fast.settings.presentation.item.TitleItem
|
||||
import dev.meloda.fast.settings.presentation.item.TitleTextItem
|
||||
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.haze
|
||||
import dev.chrisbanes.haze.hazeChild
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
@Composable
|
||||
fun SettingsRoute(
|
||||
onBack: () -> Unit,
|
||||
onLogOutButtonClicked: () -> Unit,
|
||||
onLanguageItemClicked: () -> Unit,
|
||||
viewModel: SettingsViewModel = koinViewModel<SettingsViewModelImpl>()
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val hapticType by viewModel.hapticType.collectAsStateWithLifecycle()
|
||||
|
||||
SettingsScreen(
|
||||
screenState = screenState,
|
||||
hapticType = hapticType,
|
||||
onBack = onBack,
|
||||
onHapticPerformed = viewModel::onHapticPerformed,
|
||||
onSettingsItemClicked = { key ->
|
||||
when (key) {
|
||||
SettingsKeys.KEY_APPEARANCE_LANGUAGE -> {
|
||||
onLanguageItemClicked()
|
||||
}
|
||||
|
||||
else -> viewModel.onSettingsItemClicked(key)
|
||||
}
|
||||
},
|
||||
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
|
||||
onSettingsItemValueChanged = viewModel::onSettingsItemChanged
|
||||
)
|
||||
|
||||
HandlePopups(
|
||||
performCrashPositiveClick = viewModel::onPerformCrashPositiveButtonClicked,
|
||||
performCrashDismissed = viewModel::onPerformCrashAlertDismissed,
|
||||
logoutPositiveClick = {
|
||||
viewModel.onLogOutAlertPositiveClick()
|
||||
onLogOutButtonClicked()
|
||||
},
|
||||
logoutDismissed = viewModel::onLogOutAlertDismissed,
|
||||
screenState = screenState
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalHazeMaterialsApi::class
|
||||
)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
screenState: SettingsScreenState = SettingsScreenState.EMPTY,
|
||||
hapticType: HapticType? = null,
|
||||
onBack: () -> Unit = {},
|
||||
onHapticPerformed: () -> Unit = {},
|
||||
onSettingsItemClicked: (key: String) -> Unit = {},
|
||||
onSettingsItemLongClicked: (key: String) -> Unit = {},
|
||||
onSettingsItemValueChanged: (key: String, newValue: Any?) -> Unit = { _, _ -> }
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
LaunchedEffect(hapticType) {
|
||||
if (hapticType != null) {
|
||||
view.performHapticFeedback(hapticType.getHaptic())
|
||||
onHapticPerformed()
|
||||
}
|
||||
}
|
||||
|
||||
val themeConfig = LocalThemeConfig.current
|
||||
|
||||
val hazeState = remember { HazeState() }
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = UiR.string.title_settings)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_round_arrow_back_24),
|
||||
contentDescription = "Back button"
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface.copy(
|
||||
alpha = if (themeConfig.enableBlur) 0f else 1f
|
||||
)
|
||||
),
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (themeConfig.enableBlur) {
|
||||
Modifier.hazeChild(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.thick()
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (themeConfig.enableBlur) {
|
||||
Modifier.haze(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.thick()
|
||||
)
|
||||
} else Modifier
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
|
||||
.padding(bottom = padding.calculateBottomPadding())
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||
}
|
||||
|
||||
items(
|
||||
items = screenState.settings,
|
||||
key = UiItem::key,
|
||||
contentType = { item ->
|
||||
when (item) {
|
||||
is UiItem.Title -> "title"
|
||||
is UiItem.TitleText -> "title_text"
|
||||
is UiItem.Switch -> "switch"
|
||||
is UiItem.TextField -> "text_field"
|
||||
is UiItem.List<*> -> "list"
|
||||
}
|
||||
}
|
||||
) { item ->
|
||||
when (item) {
|
||||
is UiItem.Title -> {
|
||||
TitleItem(item = item)
|
||||
}
|
||||
|
||||
is UiItem.TitleText -> {
|
||||
TitleTextItem(
|
||||
item = item,
|
||||
onClick = onSettingsItemClicked,
|
||||
onLongClick = onSettingsItemLongClicked
|
||||
)
|
||||
}
|
||||
|
||||
is UiItem.Switch -> {
|
||||
SwitchItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
|
||||
is UiItem.TextField -> {
|
||||
TextFieldItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
|
||||
is UiItem.List<*> -> {
|
||||
ListItem(
|
||||
item = item,
|
||||
onClick = { onSettingsItemClicked(item.key) },
|
||||
onLongClick = { onSettingsItemLongClicked(item.key) },
|
||||
onChanged = { onSettingsItemValueChanged(item.key, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandlePopups(
|
||||
performCrashPositiveClick: () -> Unit,
|
||||
performCrashDismissed: () -> Unit,
|
||||
logoutPositiveClick: () -> Unit,
|
||||
logoutDismissed: () -> Unit,
|
||||
screenState: SettingsScreenState
|
||||
) {
|
||||
val showOptions = screenState.showOptions
|
||||
|
||||
PerformCrashDialog(
|
||||
positiveClick = performCrashPositiveClick,
|
||||
dismiss = performCrashDismissed,
|
||||
show = showOptions.showPerformCrash
|
||||
)
|
||||
|
||||
LogOutDialog(
|
||||
positiveClick = logoutPositiveClick,
|
||||
dismiss = logoutDismissed,
|
||||
show = showOptions.showLogOut
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PerformCrashDialog(
|
||||
positiveClick: () -> Unit,
|
||||
dismiss: () -> Unit,
|
||||
show: Boolean,
|
||||
) {
|
||||
if (show) {
|
||||
MaterialDialog(
|
||||
onDismissRequest = dismiss,
|
||||
title = "Perform crash",
|
||||
text = "App will be crashed. Are you sure?",
|
||||
confirmAction = positiveClick,
|
||||
confirmText = stringResource(id = UiR.string.yes),
|
||||
cancelText = stringResource(id = UiR.string.cancel),
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogOutDialog(
|
||||
positiveClick: () -> Unit,
|
||||
dismiss: () -> Unit,
|
||||
show: Boolean
|
||||
) {
|
||||
if (show) {
|
||||
val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = dismiss,
|
||||
title = stringResource(
|
||||
id = if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
||||
else UiR.string.sign_out_confirm_title
|
||||
),
|
||||
text = stringResource(id = UiR.string.sign_out_confirm),
|
||||
confirmAction = positiveClick,
|
||||
confirmText = stringResource(
|
||||
id = if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
||||
else UiR.string.action_sign_out
|
||||
),
|
||||
cancelText = stringResource(id = UiR.string.no),
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
)
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package dev.meloda.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 dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.components.SelectionType
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
import dev.meloda.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 = LocalThemeConfig.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.enableMultiline) 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.enableMultiline) 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 dev.meloda.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 dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
|
||||
@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 = LocalThemeConfig.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.enableMultiline) 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.enableMultiline) 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))
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package dev.meloda.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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.components.ActionInvokeDismiss
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TextFieldItem(
|
||||
item: UiItem.TextField,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
onChanged: (fieldText: String) -> Unit
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
|
||||
var showDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
EditTextAlert(
|
||||
item = item,
|
||||
onAlertConfirmClicked = onChanged,
|
||||
onDismiss = { showDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
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.enableMultiline) 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.enableMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextAlert(
|
||||
item: UiItem.TextField,
|
||||
onAlertConfirmClicked: (newValue: String) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val (textFieldFocusable) = FocusRequester.createRefs()
|
||||
|
||||
var textFieldValue by remember {
|
||||
mutableStateOf(TextFieldValue(item.fieldText))
|
||||
}
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = item.title,
|
||||
confirmText = stringResource(id = R.string.ok),
|
||||
confirmAction = {
|
||||
onAlertConfirmClicked(textFieldValue.text.trim())
|
||||
},
|
||||
cancelText = stringResource(id = R.string.cancel),
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.focusRequester(textFieldFocusable)
|
||||
.weight(1f),
|
||||
value = textFieldValue,
|
||||
onValueChange = { newText ->
|
||||
textFieldValue = newText
|
||||
},
|
||||
label = { Text(text = "Value") },
|
||||
placeholder = { Text(text = "Value") },
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
textFieldFocusable.requestFocus()
|
||||
textFieldValue = textFieldValue.copy(selection = TextRange(textFieldValue.text.length))
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package dev.meloda.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 dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
|
||||
@Composable
|
||||
fun TitleItem(
|
||||
item: UiItem.Title,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
val currentTheme = LocalThemeConfig.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.enableMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package dev.meloda.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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.meloda.fast.settings.model.UiItem
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TitleTextItem(
|
||||
item: UiItem.TitleText,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (key: String) -> Unit = {},
|
||||
onLongClick: (key: String) -> Unit = {}
|
||||
) {
|
||||
if (!item.isVisible) return
|
||||
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 56.dp)
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
.combinedClickable(
|
||||
enabled = item.isEnabled,
|
||||
onClick = { onClick(item.key) },
|
||||
onLongClick = { 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 (item.isEnabled) ContentAlpha.high
|
||||
else ContentAlpha.disabled
|
||||
) {
|
||||
item.title?.let { title ->
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = if (currentTheme.enableMultiline) 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.enableMultiline) Int.MAX_VALUE else 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user