twoFa -> validation naming; fixes for preview for screens (separating view model from ui); some improvements & fixes

This commit is contained in:
2024-07-13 22:45:49 +03:00
parent dfdc48b682
commit 733627f935
98 changed files with 1611 additions and 1637 deletions
@@ -12,6 +12,7 @@ import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.data.db.AccountsRepository
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.isDebugSettingsShown
import com.meloda.app.fast.model.database.AccountEntity
import com.meloda.app.fast.settings.model.SettingsItem
@@ -44,13 +45,14 @@ interface SettingsViewModel {
fun onSettingsItemLongClicked(key: String)
fun onSettingsItemChanged(key: String, newValue: Any?)
fun onHapticsUsed()
fun onHapticPerformed()
fun onNotificationsPermissionRequested()
}
class SettingsViewModelImpl(
private val accountsRepository: AccountsRepository,
private val userSettings: UserSettings
) : SettingsViewModel, ViewModel() {
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
@@ -159,6 +161,7 @@ class SettingsViewModelImpl(
when (key) {
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
val isEnabled = (newValue as? Boolean) == true
userSettings.setLongPollBackground(isEnabled)
if (isEnabled) {
// TODO: 26/11/2023, Danil Nikolaev: implement
@@ -169,10 +172,41 @@ class SettingsViewModelImpl(
}
}
}
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useMultiline(isUsing)
}
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useAmoledThemeChanged(isUsing)
}
SettingsKeys.KEY_USE_DYNAMIC_COLORS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useDynamicColorsChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_BLUR -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useBlurChanged(isUsing)
}
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.setOnline(isUsing)
}
SettingsKeys.KEY_USE_CONTACT_NAMES -> {
val isUsing = newValue as? Boolean ?: false
userSettings.onUseContactNamesChanged(isUsing)
}
}
}
override fun onHapticsUsed() {
override fun onHapticPerformed() {
screenState.setValue { old -> old.copy(useHaptics = HapticType.None) }
}
@@ -0,0 +1,32 @@
package com.meloda.app.fast.settings.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.settings.model.OnSettingsClickListener
import com.meloda.app.fast.settings.presentation.SettingsRoute
import com.meloda.app.fast.settings.presentation.SettingsScreen
import kotlinx.serialization.Serializable
@Serializable
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)
}
@@ -1,30 +0,0 @@
package com.meloda.app.fast.settings.presentation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.meloda.app.fast.model.BaseError
import kotlinx.serialization.Serializable
@Serializable
object Settings
fun NavGraphBuilder.settingsRoute(
onError: (BaseError) -> Unit,
onBack: () -> Unit,
onNavigateToAuth: () -> Unit,
onNavigateToLanguagePicker: () -> Unit
) {
composable<Settings> {
SettingsScreen(
onError = onError,
onBack = onBack,
onNavigateToAuth = onNavigateToAuth,
onNavigateToLanguagePicker = onNavigateToLanguagePicker
)
}
}
fun NavController.navigateToSettings() {
this.navigate(Settings)
}
@@ -13,6 +13,7 @@ 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
@@ -23,7 +24,6 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -41,13 +41,9 @@ import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.isUsingDarkMode
import com.meloda.app.fast.designsystem.LocalTheme
import com.meloda.app.fast.designsystem.MaterialDialog
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.settings.HapticType
import com.meloda.app.fast.settings.SettingsViewModel
import com.meloda.app.fast.settings.SettingsViewModelImpl
import com.meloda.app.fast.settings.model.OnSettingsChangeListener
import com.meloda.app.fast.settings.model.OnSettingsClickListener
import com.meloda.app.fast.settings.model.OnSettingsLongClickListener
import com.meloda.app.fast.settings.model.SettingsItem
import com.meloda.app.fast.settings.model.SettingsScreenState
import com.meloda.app.fast.settings.presentation.items.EditTextSettingsItem
@@ -64,102 +60,94 @@ import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
import com.meloda.app.fast.designsystem.R as UiR
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class
)
@Composable
fun SettingsScreen(
onError: (BaseError) -> Unit,
fun SettingsRoute(
onBack: () -> Unit,
onNavigateToAuth: () -> Unit,
onNavigateToLanguagePicker: () -> Unit,
onLogOutButtonClicked: () -> Unit,
onLanguageItemClicked: () -> Unit,
viewModel: SettingsViewModel = koinViewModel<SettingsViewModelImpl>()
) {
val context = LocalContext.current
val view = LocalView.current
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val hapticType = screenState.useHaptics
if (hapticType != HapticType.None) {
view.performHapticFeedback(hapticType.getHaptic())
viewModel.onHapticsUsed()
}
val userSettings: UserSettings = koinInject()
LaunchedEffect(true) {
userSettings.enableDebugSettings(screenState.showDebugOptions)
}
SettingsScreen(screenState = screenState,
onBack = onBack,
onHapticPerformed = viewModel::onHapticPerformed,
onSettingsItemClicked = { key ->
when (key) {
SettingsKeys.KEY_APPEARANCE_LANGUAGE -> {
onLanguageItemClicked()
}
else -> viewModel.onSettingsItemClicked(key)
}
},
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
onSettingsItemValueChanged = { key, newValue ->
when (key) {
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
val newMode = newValue as? Int ?: 0
AppCompatDelegate.setDefaultNightMode(newMode)
val isUsing = context.getSystemService<PowerManager>()?.let { manager ->
isUsingDarkMode(
context.resources,
manager
)
} ?: false
userSettings.useDarkThemeChanged(isUsing)
}
else -> viewModel.onSettingsItemChanged(key, newValue)
}
}
)
HandlePopups(
performCrashPositiveClick = viewModel::onPerformCrashPositiveButtonClicked,
performCrashDismissed = viewModel::onPerformCrashAlertDismissed,
logoutPositiveClick = {
viewModel.onLogOutAlertPositiveClick()
onLogOutButtonClicked()
},
logoutDismissed = viewModel::onLogOutAlertDismissed,
longPollingPositiveClick = viewModel::onLongPollingAlertPositiveClicked,
longPollingDismissed = viewModel::onLongPollingAlertDismissed,
screenState = screenState
)
}
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class
)
@Composable
fun SettingsScreen(
screenState: SettingsScreenState = SettingsScreenState.EMPTY,
onBack: () -> Unit = {},
onHapticPerformed: () -> Unit = {},
onSettingsItemClicked: (key: String) -> Unit = {},
onSettingsItemLongClicked: (key: String) -> Unit = {},
onSettingsItemValueChanged: (key: String, newValue: Any?) -> Unit = { _, _ -> }
) {
val view = LocalView.current
val hapticType = screenState.useHaptics
LaunchedEffect(hapticType) {
if (hapticType != HapticType.None) {
view.performHapticFeedback(hapticType.getHaptic())
onHapticPerformed()
}
}
val currentTheme = LocalTheme.current
val settingsList = screenState.settings
val clickListener = OnSettingsClickListener { key ->
when (key) {
SettingsKeys.KEY_APPEARANCE_LANGUAGE -> {
onNavigateToLanguagePicker()
}
else -> viewModel.onSettingsItemClicked(key)
}
}
val longClickListener = OnSettingsLongClickListener(viewModel::onSettingsItemLongClicked)
val changeListener = OnSettingsChangeListener { key, newValue ->
when (key) {
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useMultiline(isUsing)
}
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
val newMode = newValue as? Int ?: return@OnSettingsChangeListener
AppCompatDelegate.setDefaultNightMode(newMode)
val isUsing = context.getSystemService<PowerManager>()?.let { manager ->
isUsingDarkMode(
context.resources,
manager
)
} ?: false
userSettings.useDarkThemeChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useAmoledThemeChanged(isUsing)
}
SettingsKeys.KEY_USE_DYNAMIC_COLORS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useDynamicColorsChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_BLUR -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useBlurChanged(isUsing)
}
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
val isUsing = newValue as? Boolean ?: false
userSettings.setLongPollBackground(isUsing)
}
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.setOnline(isUsing)
}
SettingsKeys.KEY_USE_CONTACT_NAMES -> {
val isUsing = newValue as? Boolean ?: false
userSettings.onUseContactNamesChanged(isUsing)
}
else -> viewModel.onSettingsItemChanged(key, newValue)
}
}
val hazeState = remember { HazeState() }
@@ -167,19 +155,16 @@ fun SettingsScreen(
modifier = Modifier.fillMaxSize(),
contentWindowInsets = WindowInsets.statusBars,
topBar = {
val title = @Composable { Text(text = stringResource(id = UiR.string.title_settings)) }
val navigationIcon = @Composable {
IconButton(onClick = onBack) {
Icon(
painter = painterResource(id = UiR.drawable.ic_round_arrow_back_24),
contentDescription = "Back button"
)
}
}
TopAppBar(
title = title,
navigationIcon = navigationIcon,
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 (currentTheme.usingBlur) 0f else 1f
@@ -215,33 +200,23 @@ fun SettingsScreen(
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
.padding(bottom = padding.calculateBottomPadding())
) {
item {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
items(
count = settingsList.size,
// key = { index ->
// val item = settingsList[index]
// requireNotNull(item.title ?: item.summary)
// },
contentType = { index ->
when (settingsList[index]) {
is SettingsItem.ListItem -> "listitem"
items = screenState.settings,
key = { item -> item.key },
contentType = { item ->
when (item) {
is SettingsItem.ListItem -> "list_item"
is SettingsItem.Switch -> "switch"
is SettingsItem.TextField -> "textfield"
is SettingsItem.TextField -> "text_field"
is SettingsItem.Title -> "title"
is SettingsItem.TitleSummary -> "titlesummary"
is SettingsItem.TitleSummary -> "title_summary"
}
}
) { index ->
val needToShowSpacer by remember {
derivedStateOf {
index == 0
}
}
if (needToShowSpacer) {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
when (val item = settingsList[index]) {
) { item ->
when (item) {
is SettingsItem.Title -> TitleSettingsItem(
item = item,
isMultiline = currentTheme.multiline,
@@ -251,67 +226,47 @@ fun SettingsScreen(
is SettingsItem.TitleSummary -> TitleSummarySettingsItem(
item = item,
isMultiline = currentTheme.multiline,
onSettingsClickListener = clickListener,
onSettingsLongClickListener = longClickListener,
onSettingsClickListener = onSettingsItemClicked,
onSettingsLongClickListener = onSettingsItemLongClicked,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
)
is SettingsItem.Switch -> SwitchSettingsItem(
item = item,
isMultiline = currentTheme.multiline,
onSettingsClickListener = clickListener,
onSettingsLongClickListener = longClickListener,
onSettingsChangeListener = changeListener,
onSettingsClickListener = onSettingsItemClicked,
onSettingsLongClickListener = onSettingsItemLongClicked,
onSettingsChangeListener = onSettingsItemValueChanged,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
)
is SettingsItem.TextField -> EditTextSettingsItem(
item = item,
isMultiline = currentTheme.multiline,
onSettingsClickListener = clickListener,
onSettingsLongClickListener = longClickListener,
onSettingsChangeListener = changeListener,
onSettingsClickListener = onSettingsItemClicked,
onSettingsLongClickListener = onSettingsItemLongClicked,
onSettingsChangeListener = onSettingsItemValueChanged,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
)
is SettingsItem.ListItem -> ListSettingsItem(
item = item,
isMultiline = currentTheme.multiline,
onSettingsClickListener = clickListener,
onSettingsLongClickListener = longClickListener,
onSettingsChangeListener = changeListener,
onSettingsClickListener = onSettingsItemClicked,
onSettingsLongClickListener = onSettingsItemLongClicked,
onSettingsChangeListener = onSettingsItemValueChanged,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
)
}
}
val showBottomNavigationBarsSpacer by remember {
derivedStateOf {
index == settingsList.size - 1
}
}
if (showBottomNavigationBarsSpacer) {
Spacer(modifier = Modifier.navigationBarsPadding())
}
item {
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
}
HandlePopups(
performCrashPositiveClick = viewModel::onPerformCrashPositiveButtonClicked,
performCrashDismissed = viewModel::onPerformCrashAlertDismissed,
logoutPositiveClick = {
viewModel.onLogOutAlertPositiveClick()
onNavigateToAuth()
},
logoutDismissed = viewModel::onLogOutAlertDismissed,
longPollingPositiveClick = viewModel::onLongPollingAlertPositiveClicked,
longPollingDismissed = viewModel::onLongPollingAlertDismissed,
screenState = screenState
)
}
// TODO: 12/04/2024, Danil Nikolaev: rewrite to UiAction
@Composable
fun HandlePopups(
performCrashPositiveClick: () -> Unit,