refactor default alerts

This commit is contained in:
2024-07-15 06:02:25 +03:00
parent 654f47bb94
commit eaf609c475
10 changed files with 366 additions and 235 deletions
@@ -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
)
}
@@ -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
)
}
@@ -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) {
@@ -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
}
@@ -32,15 +32,10 @@ interface SettingsViewModel {
val isLongPollBackgroundEnabled: StateFlow<Boolean?>
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) {
@@ -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
)
}
}
@@ -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
)
}
}