From eaf609c475287dcc47ef5364947035b7c802d761 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Mon, 15 Jul 2024 06:02:25 +0300 Subject: [PATCH] refactor default alerts --- .../app/fast/designsystem/Extensions.kt | 24 ++ .../app/fast/designsystem/MaterialDialog.kt | 233 ++++++++++++++++-- .../src/main/res/values/strings.xml | 5 +- .../captcha/presentation/CaptchaScreen.kt | 20 +- .../presentation/ValidationScreen.kt | 20 +- .../conversations/ConversationsViewModel.kt | 111 +++++---- .../presentation/ConversationsScreen.kt | 87 ++----- .../app/fast/settings/SettingsViewModel.kt | 31 +-- .../settings/model/SettingsShowOptions.kt | 2 - .../settings/presentation/SettingsScreen.kt | 68 ++--- 10 files changed, 366 insertions(+), 235 deletions(-) diff --git a/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/Extensions.kt b/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/Extensions.kt index 194a731a..a8313c81 100644 --- a/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/Extensions.kt +++ b/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/Extensions.kt @@ -3,7 +3,13 @@ package com.meloda.app.fast.designsystem import android.content.res.Configuration import android.view.KeyEvent import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalContext @@ -77,3 +83,21 @@ fun Modifier.handleEnterKey( action.invoke() } else false } + +@Composable +fun LazyListState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } + var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +} diff --git a/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/MaterialDialog.kt b/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/MaterialDialog.kt index 62fa1341..4111591c 100644 --- a/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/MaterialDialog.kt +++ b/core/designsystem/src/main/kotlin/com/meloda/app/fast/designsystem/MaterialDialog.kt @@ -37,6 +37,208 @@ import com.meloda.app.fast.common.UiText import com.meloda.app.fast.common.parseString import com.meloda.app.fast.designsystem.ImmutableList.Companion.toImmutableList +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MaterialDialog( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + confirmText: String? = null, + confirmAction: (() -> Unit)? = null, + cancelText: String? = null, + cancelAction: (() -> Unit)? = null, + neutralText: String? = null, + neutralAction: (() -> Unit)? = null, + title: String? = null, + text: String? = null, + itemsSelectionType: ItemsSelectionType = ItemsSelectionType.None, + items: ImmutableList = ImmutableList.empty(), + preSelectedItems: ImmutableList = ImmutableList.empty(), + onItemClick: ((index: Int) -> Unit)? = null, + properties: DialogProperties = DialogProperties(), + actionInvokeDismiss: ActionInvokeDismiss = ActionInvokeDismiss.IfNoAction, + customContent: (ColumnScope.() -> Unit)? = null +) { + var alertItems by remember { + mutableStateOf( + items.mapIndexed { index, title -> + DialogItem( + title, + preSelectedItems.contains(index) + ) + } + ) + } + + BasicAlertDialog( + onDismissRequest = onDismissRequest, + modifier = modifier, + properties = properties + ) { + val scrollState = rememberScrollState() + val canScrollBackward by remember { derivedStateOf { scrollState.value > 0 } } + val canScrollForward by remember { derivedStateOf { scrollState.value < scrollState.maxValue } } + + Surface( + modifier = Modifier.fillMaxWidth(), + color = AlertDialogDefaults.containerColor, + shape = AlertDialogDefaults.shape, + tonalElevation = AlertDialogDefaults.TonalElevation + ) { + Column(modifier = Modifier.padding(bottom = 10.dp)) { + if (title != null) { + Spacer(modifier = Modifier.height(20.dp)) + + Row { + Spacer(modifier = Modifier.width(24.dp)) + Text( + modifier = Modifier.weight(1f), + text = title, + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.width(20.dp)) + } + } + + if (canScrollBackward) { + HorizontalDivider(modifier = Modifier.fillMaxWidth()) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = false) + .verticalScroll(scrollState) + ) { + Spacer(modifier = Modifier.height(8.dp)) + + if (text != null && title == null) { + Spacer(modifier = Modifier.height(20.dp)) + } + + if (text != null) { + Row { + Spacer(modifier = Modifier.width(24.dp)) + Text( + modifier = Modifier.weight(1f), + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.width(20.dp)) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + if (alertItems.isNotEmpty()) { + Spacer(modifier = Modifier.height(4.dp)) + AlertItems( + selectionType = itemsSelectionType, + items = alertItems, + onItemClick = { index -> + onItemClick?.invoke(index) + + if (itemsSelectionType == ItemsSelectionType.None) { + onDismissRequest.invoke() + } else { + val newItems = + alertItems.mapIndexed { itemIndex, item -> + item.copy(isSelected = itemIndex == index) + } + + alertItems = newItems + } + }, + onItemCheckedChanged = { index -> + val newItems = alertItems.toMutableList() + val oldItem = newItems[index] + newItems[index] = + oldItem.copy(isSelected = !oldItem.isSelected) + + alertItems = newItems.toImmutableList() + } + ) + Spacer(modifier = Modifier.height(10.dp)) + } else { + if (customContent != null) { + Spacer(modifier = Modifier.height(4.dp)) + customContent.invoke(this) + Spacer(modifier = Modifier.height(10.dp)) + } + } + } + + if (canScrollForward) { + HorizontalDivider(modifier = Modifier.fillMaxWidth()) + } + + Row { + Spacer(modifier = Modifier.width(20.dp)) + if (neutralText != null) { + TextButton( + onClick = { + neutralAction?.invoke() ?: kotlin.run { + if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) { + onDismissRequest() + } + } + + if (actionInvokeDismiss == ActionInvokeDismiss.Always) { + onDismissRequest() + } + } + ) { + Text(text = neutralText) + } + } + + Spacer(modifier = Modifier.weight(1f)) + + if (cancelText != null) { + TextButton( + onClick = { + cancelAction?.invoke() ?: kotlin.run { + if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) { + onDismissRequest() + } + } + + if (actionInvokeDismiss == ActionInvokeDismiss.Always) { + onDismissRequest() + } + } + ) { + Text(text = cancelText) + } + } + + Spacer(modifier = Modifier.width(2.dp)) + + if (confirmText != null) { + TextButton( + onClick = { + confirmAction?.invoke() ?: kotlin.run { + if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) { + onDismissRequest() + } + } + + if (actionInvokeDismiss == ActionInvokeDismiss.Always) { + onDismissRequest() + } + } + ) { + Text(text = confirmText) + } + } + + Spacer(modifier = Modifier.width(20.dp)) + } + } + } + } +} + // TODO: 08.04.2023, Danil Nikolaev: refactor this @Deprecated("need refactoring") @OptIn(ExperimentalMaterial3Api::class) @@ -84,7 +286,7 @@ fun MaterialDialog( ) } - AlertAnimation(visible = isVisible) { + if (isVisible) { BasicAlertDialog( onDismissRequest = onDismissRequest, properties = properties @@ -250,21 +452,6 @@ fun MaterialDialog( } } -@Composable -fun AlertAnimation( - visible: Boolean, - content: @Composable () -> Unit -) { - if (visible) content() -// AnimatedVisibility( -// visible = visible, -// enter = fadeIn(animationSpec = tween(400)) + -// scaleIn(animationSpec = tween(400)), -// exit = fadeOut(animationSpec = tween(150)), -// content = content -// ) -} - @Composable private fun AlertItems( selectionType: ItemsSelectionType, @@ -324,8 +511,14 @@ data class DialogItem( val isSelected: Boolean ) -sealed interface ItemsSelectionType { - data object Single : ItemsSelectionType - data object Multi : ItemsSelectionType - data object None : ItemsSelectionType +sealed class ActionInvokeDismiss { + data object Never : ActionInvokeDismiss() + data object IfNoAction : ActionInvokeDismiss() + data object Always : ActionInvokeDismiss() +} + +sealed class ItemsSelectionType { + data object Single : ItemsSelectionType() + data object Multi : ItemsSelectionType() + data object None : ItemsSelectionType() } diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 8c9e7ec1..da6e8206 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -259,4 +259,7 @@ Message update service Message update service notifications - + Confirmation + Are you sure? Captcha process will be cancelled + Are you sure? Validation process will be cancelled + diff --git a/feature/auth/captcha/src/main/kotlin/com/meloda/app/fast/auth/captcha/presentation/CaptchaScreen.kt b/feature/auth/captcha/src/main/kotlin/com/meloda/app/fast/auth/captcha/presentation/CaptchaScreen.kt index 076cbd52..2a15ce99 100644 --- a/feature/auth/captcha/src/main/kotlin/com/meloda/app/fast/auth/captcha/presentation/CaptchaScreen.kt +++ b/feature/auth/captcha/src/main/kotlin/com/meloda/app/fast/auth/captcha/presentation/CaptchaScreen.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp @@ -50,7 +51,7 @@ import coil.request.ImageRequest import com.meloda.app.fast.auth.captcha.CaptchaViewModel import com.meloda.app.fast.auth.captcha.CaptchaViewModelImpl import com.meloda.app.fast.auth.captcha.model.CaptchaScreenState -import com.meloda.app.fast.common.UiText +import com.meloda.app.fast.designsystem.ActionInvokeDismiss import com.meloda.app.fast.designsystem.MaterialDialog import com.meloda.app.fast.designsystem.TextFieldErrorText import org.koin.androidx.compose.koinViewModel @@ -89,7 +90,7 @@ fun CaptchaScreen( onTextFieldDoneAction: () -> Unit = {}, onDoneButtonClicked: () -> Unit = {} ) { - var confirmedExit by rememberSaveable { + var confirmedExit by remember { mutableStateOf(false) } @@ -111,14 +112,13 @@ fun CaptchaScreen( if (showExitAlert) { MaterialDialog( - onDismissAction = { showExitAlert = false }, - title = UiText.Simple("Confirmation"), - text = UiText.Simple("Are you sure? Captcha process will be cancelled."), - confirmText = UiText.Resource(UiR.string.yes), - confirmAction = { - confirmedExit = true - }, - cancelText = UiText.Resource(UiR.string.no) + onDismissRequest = { showExitAlert = false }, + title = stringResource(id = UiR.string.warning_confirmation), + text = stringResource(id = UiR.string.captcha_exit_warning), + confirmAction = { confirmedExit = true }, + confirmText = stringResource(id = UiR.string.yes), + cancelText = stringResource(id = UiR.string.no), + actionInvokeDismiss = ActionInvokeDismiss.Always ) } diff --git a/feature/auth/validation/src/main/kotlin/com/meloda/app/fast/auth/validation/presentation/ValidationScreen.kt b/feature/auth/validation/src/main/kotlin/com/meloda/app/fast/auth/validation/presentation/ValidationScreen.kt index 77030af6..55a6a6c3 100644 --- a/feature/auth/validation/src/main/kotlin/com/meloda/app/fast/auth/validation/presentation/ValidationScreen.kt +++ b/feature/auth/validation/src/main/kotlin/com/meloda/app/fast/auth/validation/presentation/ValidationScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue @@ -46,7 +47,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.meloda.app.fast.auth.validation.ValidationViewModel import com.meloda.app.fast.auth.validation.ValidationViewModelImpl import com.meloda.app.fast.auth.validation.model.ValidationScreenState -import com.meloda.app.fast.common.UiText +import com.meloda.app.fast.designsystem.ActionInvokeDismiss import com.meloda.app.fast.designsystem.MaterialDialog import com.meloda.app.fast.designsystem.TextFieldErrorText import com.meloda.app.fast.designsystem.getString @@ -96,7 +97,7 @@ fun ValidationScreen( ) { val focusManager = LocalFocusManager.current - var confirmedExit by rememberSaveable { + var confirmedExit by remember { mutableStateOf(false) } @@ -118,14 +119,13 @@ fun ValidationScreen( if (showExitAlert) { MaterialDialog( - onDismissAction = { showExitAlert = false }, - title = UiText.Simple("Confirmation"), - text = UiText.Simple("Are you sure? Authorization process will be cancelled."), - confirmText = UiText.Resource(UiR.string.yes), - confirmAction = { - confirmedExit = true - }, - cancelText = UiText.Resource(UiR.string.no) + onDismissRequest = { showExitAlert = false }, + title = stringResource(id = UiR.string.warning_confirmation), + text = stringResource(id = UiR.string.validation_exit_warning), + confirmAction = { confirmedExit = true }, + confirmText = stringResource(id = UiR.string.yes), + cancelText = stringResource(id = UiR.string.no), + actionInvokeDismiss = ActionInvokeDismiss.Always ) } diff --git a/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/ConversationsViewModel.kt b/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/ConversationsViewModel.kt index f1141909..a06533b5 100644 --- a/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/ConversationsViewModel.kt +++ b/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/ConversationsViewModel.kt @@ -47,8 +47,7 @@ interface ConversationsViewModel { fun onPaginationConditionsMet() fun onDeleteDialogDismissed() - - fun onDeleteDialogPositiveClick(conversationId: Int) + fun onDeleteDialogPositiveClick() fun onRefresh() @@ -56,7 +55,8 @@ interface ConversationsViewModel { fun onConversationItemLongClick(conversation: UiConversation) fun onPinDialogDismissed() - fun onPinDialogPositiveClick(conversation: UiConversation) + fun onPinDialogPositiveClick() + fun onOptionClicked(conversation: UiConversation, option: ConversationOption) fun onErrorConsumed() @@ -104,9 +104,11 @@ class ConversationsViewModelImpl( emitShowOptions { old -> old.copy(showDeleteDialog = null) } } - override fun onDeleteDialogPositiveClick(conversationId: Int) { + override fun onDeleteDialogPositiveClick() { + val conversationId = screenState.value.showOptions.showDeleteDialog ?: return deleteConversation(conversationId) hideOptions(conversationId) + onDeleteDialogDismissed() } override fun onRefresh() { @@ -168,9 +170,11 @@ class ConversationsViewModelImpl( emitShowOptions { old -> old.copy(showPinDialog = null) } } - override fun onPinDialogPositiveClick(conversation: UiConversation) { + override fun onPinDialogPositiveClick() { + val conversation = screenState.value.showOptions.showPinDialog ?: return pinConversation(conversation.id, !conversation.isPinned) hideOptions(conversation.id) + onPinDialogDismissed() } override fun onOptionClicked(conversation: UiConversation, option: ConversationOption) { @@ -222,64 +226,65 @@ class ConversationsViewModelImpl( private fun loadConversations( offset: Int = currentOffset.value ) { - conversationsUseCase.getConversations(count = LOAD_COUNT, offset = offset).listenValue { state -> - state.processState( - error = { error -> - if (error is State.Error.ApiError) { - when (error.errorCode) { - VkErrorCodes.UserAuthorizationFailed -> { - baseError.setValue { BaseError.SessionExpired } + conversationsUseCase.getConversations(count = LOAD_COUNT, offset = offset) + .listenValue { state -> + state.processState( + error = { error -> + if (error is State.Error.ApiError) { + when (error.errorCode) { + VkErrorCodes.UserAuthorizationFailed -> { + baseError.setValue { BaseError.SessionExpired } + } + + else -> Unit } - - else -> Unit } - } - }, - success = { response -> - val itemsCountSufficient = response.size == LOAD_COUNT - canPaginate.setValue { itemsCountSufficient } + }, + success = { response -> + val itemsCountSufficient = response.size == LOAD_COUNT + canPaginate.setValue { itemsCountSufficient } - val paginationExhausted = !itemsCountSufficient && - screenState.value.conversations.isNotEmpty() + val paginationExhausted = !itemsCountSufficient && + screenState.value.conversations.isNotEmpty() - imagesToPreload.setValue { - response.mapNotNull { it.extractAvatar().extractUrl() } - } - conversationsUseCase.storeConversations(response) - - val loadedConversations = response.map { - it.asPresentation( - resources, - userSettings.useContactNames.value - ) - } - - val newState = screenState.value.copy( - isPaginationExhausted = paginationExhausted - ) - if (offset == 0) { - conversations.emit(response) - screenState.setValue { - newState.copy(conversations = loadedConversations) + imagesToPreload.setValue { + response.mapNotNull { it.extractAvatar().extractUrl() } } - } else { - conversations.emit(conversations.value.plus(response)) - screenState.setValue { - newState.copy( - conversations = newState.conversations.plus(loadedConversations) + conversationsUseCase.storeConversations(response) + + val loadedConversations = response.map { + it.asPresentation( + resources, + userSettings.useContactNames.value ) } - } - } - ) - screenState.setValue { old -> - old.copy( - isLoading = offset == 0 && state.isLoading(), - isPaginating = offset > 0 && state.isLoading() + val newState = screenState.value.copy( + isPaginationExhausted = paginationExhausted + ) + if (offset == 0) { + conversations.emit(response) + screenState.setValue { + newState.copy(conversations = loadedConversations) + } + } else { + conversations.emit(conversations.value.plus(response)) + screenState.setValue { + newState.copy( + conversations = newState.conversations.plus(loadedConversations) + ) + } + } + } ) + + screenState.setValue { old -> + old.copy( + isLoading = offset == 0 && state.isLoading(), + isPaginating = offset > 0 && state.isLoading() + ) + } } - } } private fun deleteConversation(peerId: Int) { diff --git a/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/presentation/ConversationsScreen.kt b/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/presentation/ConversationsScreen.kt index 2807b0e9..f0a41a2d 100644 --- a/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/presentation/ConversationsScreen.kt +++ b/feature/conversations/src/main/kotlin/com/meloda/app/fast/conversations/presentation/ConversationsScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.MoreVert @@ -68,7 +67,6 @@ import androidx.core.view.HapticFeedbackConstantsCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.imageLoader import coil.request.ImageRequest -import com.meloda.app.fast.common.UiText import com.meloda.app.fast.conversations.ConversationsViewModel import com.meloda.app.fast.conversations.ConversationsViewModelImpl import com.meloda.app.fast.conversations.model.ConversationOption @@ -79,6 +77,7 @@ import com.meloda.app.fast.designsystem.LocalHazeState import com.meloda.app.fast.designsystem.LocalTheme import com.meloda.app.fast.designsystem.MaterialDialog import com.meloda.app.fast.designsystem.components.FullScreenLoader +import com.meloda.app.fast.designsystem.isScrollingUp import com.meloda.app.fast.model.BaseError import com.meloda.app.fast.ui.ErrorView import dev.chrisbanes.haze.haze @@ -398,72 +397,30 @@ fun HandleDialogs( val showOptions = screenState.showOptions if (showOptions.showDeleteDialog != null) { - val conversationId = showOptions.showDeleteDialog - DeleteDialog( - conversationId = conversationId, - viewModel = viewModel + MaterialDialog( + onDismissRequest = viewModel::onDeleteDialogDismissed, + title = stringResource(id = UiR.string.confirm_delete_conversation), + confirmAction = viewModel::onDeleteDialogPositiveClick, + confirmText = stringResource(id = UiR.string.action_delete), + cancelText = stringResource(id = UiR.string.cancel) ) } - showOptions.showPinDialog?.let { conversation -> - PinDialog( - conversation = conversation, - viewModel = viewModel + if (showOptions.showPinDialog != null) { + val conversation = showOptions.showPinDialog + + MaterialDialog( + onDismissRequest = viewModel::onPinDialogDismissed, + title = stringResource( + id = if (conversation.isPinned) UiR.string.confirm_unpin_conversation + else UiR.string.confirm_pin_conversation + ), + confirmAction = viewModel::onPinDialogPositiveClick, + confirmText = stringResource( + id = if (conversation.isPinned) UiR.string.action_unpin + else UiR.string.action_pin + ), + cancelText = stringResource(id = UiR.string.cancel) ) } } - -@Composable -fun DeleteDialog( - conversationId: Int, - viewModel: ConversationsViewModel -) { - MaterialDialog( - title = UiText.Resource(UiR.string.confirm_delete_conversation), - confirmText = UiText.Resource(UiR.string.action_delete), - confirmAction = { viewModel.onDeleteDialogPositiveClick(conversationId) }, - cancelText = UiText.Resource(UiR.string.cancel), - onDismissAction = viewModel::onDeleteDialogDismissed - ) -} - -@Composable -fun PinDialog( - conversation: UiConversation, - viewModel: ConversationsViewModel -) { - MaterialDialog( - title = UiText.Resource( - if (conversation.isPinned) UiR.string.confirm_unpin_conversation - else UiR.string.confirm_pin_conversation - ), - confirmText = UiText.Resource( - if (conversation.isPinned) UiR.string.action_unpin - else UiR.string.action_pin - ), - confirmAction = { - viewModel.onPinDialogPositiveClick(conversation) - }, - cancelText = UiText.Resource(UiR.string.cancel), - onDismissAction = viewModel::onPinDialogDismissed - ) -} - - -@Composable -private fun LazyListState.isScrollingUp(): Boolean { - var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } - var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } - return remember(this) { - derivedStateOf { - if (previousIndex != firstVisibleItemIndex) { - previousIndex > firstVisibleItemIndex - } else { - previousScrollOffset >= firstVisibleItemScrollOffset - }.also { - previousIndex = firstVisibleItemIndex - previousScrollOffset = firstVisibleItemScrollOffset - } - } - }.value -} diff --git a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/SettingsViewModel.kt index 7644a657..5e43b6ea 100644 --- a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/SettingsViewModel.kt @@ -32,15 +32,10 @@ interface SettingsViewModel { val isLongPollBackgroundEnabled: StateFlow fun onLogOutAlertDismissed() - - fun onPerformCrashAlertDismissed() - - fun onPerformCrashPositiveButtonClicked() - fun onLogOutAlertPositiveClick() - fun onLongPollingAlertPositiveClicked() - fun onLongPollingAlertDismissed() + fun onPerformCrashAlertDismissed() + fun onPerformCrashPositiveButtonClicked() fun onSettingsItemClicked(key: String) fun onSettingsItemLongClicked(key: String) @@ -68,14 +63,6 @@ class SettingsViewModelImpl( emitShowOptions { old -> old.copy(showLogOut = false) } } - override fun onPerformCrashAlertDismissed() { - emitShowOptions { old -> old.copy(showPerformCrash = false) } - } - - override fun onPerformCrashPositiveButtonClicked() { - throw Exception("Test exception") - } - override fun onLogOutAlertPositiveClick() { viewModelScope.launch(Dispatchers.IO) { accountsRepository.storeAccounts( @@ -93,18 +80,12 @@ class SettingsViewModelImpl( } } - override fun onLongPollingAlertPositiveClicked() { - screenState.setValue { old -> old.copy(isNeedToRequestNotificationPermission = true) } + override fun onPerformCrashAlertDismissed() { + emitShowOptions { old -> old.copy(showPerformCrash = false) } } - override fun onLongPollingAlertDismissed() { - screenState.setValue { old -> - old.copy( - showOptions = old.showOptions.copy( - showLongPollNotifications = false - ) - ) - } + override fun onPerformCrashPositiveButtonClicked() { + throw Exception("Test exception") } override fun onSettingsItemClicked(key: String) { diff --git a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/model/SettingsShowOptions.kt b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/model/SettingsShowOptions.kt index 2c805af2..0aa33bb7 100644 --- a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/model/SettingsShowOptions.kt +++ b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/model/SettingsShowOptions.kt @@ -3,14 +3,12 @@ package com.meloda.app.fast.settings.model data class SettingsShowOptions( val showLogOut: Boolean, val showPerformCrash: Boolean, - val showLongPollNotifications: Boolean ) { companion object { val EMPTY: SettingsShowOptions = SettingsShowOptions( showLogOut = false, showPerformCrash = false, - showLongPollNotifications = false ) } } diff --git a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/presentation/SettingsScreen.kt b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/presentation/SettingsScreen.kt index 50c9b706..d78f4c0a 100644 --- a/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/presentation/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/com/meloda/app/fast/settings/presentation/SettingsScreen.kt @@ -34,11 +34,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection import androidx.core.content.getSystemService import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.meloda.app.fast.common.UiText import com.meloda.app.fast.common.UserConfig import com.meloda.app.fast.datastore.SettingsKeys import com.meloda.app.fast.datastore.UserSettings import com.meloda.app.fast.datastore.isUsingDarkMode +import com.meloda.app.fast.designsystem.ActionInvokeDismiss import com.meloda.app.fast.designsystem.LocalTheme import com.meloda.app.fast.designsystem.MaterialDialog import com.meloda.app.fast.settings.HapticType @@ -118,8 +118,6 @@ fun SettingsRoute( onLogOutButtonClicked() }, logoutDismissed = viewModel::onLogOutAlertDismissed, - longPollingPositiveClick = viewModel::onLongPollingAlertPositiveClicked, - longPollingDismissed = viewModel::onLongPollingAlertDismissed, screenState = screenState ) } @@ -273,8 +271,6 @@ fun HandlePopups( performCrashDismissed: () -> Unit, logoutPositiveClick: () -> Unit, logoutDismissed: () -> Unit, - longPollingPositiveClick: () -> Unit, - longPollingDismissed: () -> Unit, screenState: SettingsScreenState ) { val showOptions = screenState.showOptions @@ -290,12 +286,6 @@ fun HandlePopups( dismiss = logoutDismissed, show = showOptions.showLogOut ) - - LongPollingNotificationsPermission( - positiveClick = longPollingPositiveClick, - dismiss = longPollingDismissed, - show = showOptions.showLongPollNotifications - ) } @Composable @@ -306,12 +296,13 @@ fun PerformCrashDialog( ) { if (show) { MaterialDialog( - title = UiText.Simple("Perform Crash"), - text = UiText.Simple("App will be crashed. Are you sure?"), - confirmText = UiText.Resource(UiR.string.yes), + onDismissRequest = dismiss, + title = "Perform crash", + text = "App will be crashed. Are you sure?", confirmAction = positiveClick, - cancelText = UiText.Resource(UiR.string.cancel), - onDismissAction = dismiss + confirmText = stringResource(id = UiR.string.yes), + cancelText = stringResource(id = UiR.string.cancel), + actionInvokeDismiss = ActionInvokeDismiss.Always ) } } @@ -325,41 +316,20 @@ fun LogOutDialog( if (show) { val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY - val title = UiText.Resource( - if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry - else UiR.string.sign_out_confirm_title - ) - - val positiveText = UiText.Resource( - if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry - else UiR.string.action_sign_out - ) - MaterialDialog( - title = title, - text = UiText.Resource(UiR.string.sign_out_confirm), - confirmText = positiveText, + 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, - cancelText = UiText.Resource(UiR.string.cancel), - onDismissAction = dismiss - ) - } -} - -@Composable -fun LongPollingNotificationsPermission( - positiveClick: () -> Unit, - dismiss: () -> Unit, - show: Boolean -) { - if (show) { - MaterialDialog( - title = UiText.Resource(UiR.string.warning), - text = UiText.Simple("Long polling in background required notifications permission on Android 13 and up"), - confirmText = UiText.Simple("Grant"), - confirmAction = positiveClick, - cancelText = UiText.Resource(UiR.string.cancel), - onDismissAction = dismiss + 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 ) } }