forked from melod1n/fast-messenger
refactor default alerts
This commit is contained in:
@@ -3,7 +3,13 @@ package com.meloda.app.fast.designsystem
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -77,3 +83,21 @@ fun Modifier.handleEnterKey(
|
|||||||
action.invoke()
|
action.invoke()
|
||||||
} else false
|
} 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
|
||||||
|
}
|
||||||
|
|||||||
+213
-20
@@ -37,6 +37,208 @@ import com.meloda.app.fast.common.UiText
|
|||||||
import com.meloda.app.fast.common.parseString
|
import com.meloda.app.fast.common.parseString
|
||||||
import com.meloda.app.fast.designsystem.ImmutableList.Companion.toImmutableList
|
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<String> = ImmutableList.empty(),
|
||||||
|
preSelectedItems: ImmutableList<Int> = 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
|
// TODO: 08.04.2023, Danil Nikolaev: refactor this
|
||||||
@Deprecated("need refactoring")
|
@Deprecated("need refactoring")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -84,7 +286,7 @@ fun MaterialDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertAnimation(visible = isVisible) {
|
if (isVisible) {
|
||||||
BasicAlertDialog(
|
BasicAlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
properties = properties
|
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
|
@Composable
|
||||||
private fun AlertItems(
|
private fun AlertItems(
|
||||||
selectionType: ItemsSelectionType,
|
selectionType: ItemsSelectionType,
|
||||||
@@ -324,8 +511,14 @@ data class DialogItem(
|
|||||||
val isSelected: Boolean
|
val isSelected: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface ItemsSelectionType {
|
sealed class ActionInvokeDismiss {
|
||||||
data object Single : ItemsSelectionType
|
data object Never : ActionInvokeDismiss()
|
||||||
data object Multi : ItemsSelectionType
|
data object IfNoAction : ActionInvokeDismiss()
|
||||||
data object None : ItemsSelectionType
|
data object Always : ActionInvokeDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ItemsSelectionType {
|
||||||
|
data object Single : ItemsSelectionType()
|
||||||
|
data object Multi : ItemsSelectionType()
|
||||||
|
data object None : ItemsSelectionType()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,4 +259,7 @@
|
|||||||
|
|
||||||
<string name="notification_channel_long_polling_service_name">Message update service</string>
|
<string name="notification_channel_long_polling_service_name">Message update service</string>
|
||||||
<string name="notification_channel_long_polling_service_description">Message update service notifications</string>
|
<string name="notification_channel_long_polling_service_description">Message update service notifications</string>
|
||||||
</resources>
|
<string name="warning_confirmation">Confirmation</string>
|
||||||
|
<string name="captcha_exit_warning">Are you sure? Captcha process will be cancelled</string>
|
||||||
|
<string name="validation_exit_warning">Are you sure? Validation process will be cancelled</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
+10
-10
@@ -41,6 +41,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
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.ImeAction
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
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.CaptchaViewModel
|
||||||
import com.meloda.app.fast.auth.captcha.CaptchaViewModelImpl
|
import com.meloda.app.fast.auth.captcha.CaptchaViewModelImpl
|
||||||
import com.meloda.app.fast.auth.captcha.model.CaptchaScreenState
|
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.MaterialDialog
|
||||||
import com.meloda.app.fast.designsystem.TextFieldErrorText
|
import com.meloda.app.fast.designsystem.TextFieldErrorText
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
@@ -89,7 +90,7 @@ fun CaptchaScreen(
|
|||||||
onTextFieldDoneAction: () -> Unit = {},
|
onTextFieldDoneAction: () -> Unit = {},
|
||||||
onDoneButtonClicked: () -> Unit = {}
|
onDoneButtonClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
var confirmedExit by rememberSaveable {
|
var confirmedExit by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +112,13 @@ fun CaptchaScreen(
|
|||||||
|
|
||||||
if (showExitAlert) {
|
if (showExitAlert) {
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissAction = { showExitAlert = false },
|
onDismissRequest = { showExitAlert = false },
|
||||||
title = UiText.Simple("Confirmation"),
|
title = stringResource(id = UiR.string.warning_confirmation),
|
||||||
text = UiText.Simple("Are you sure? Captcha process will be cancelled."),
|
text = stringResource(id = UiR.string.captcha_exit_warning),
|
||||||
confirmText = UiText.Resource(UiR.string.yes),
|
confirmAction = { confirmedExit = true },
|
||||||
confirmAction = {
|
confirmText = stringResource(id = UiR.string.yes),
|
||||||
confirmedExit = true
|
cancelText = stringResource(id = UiR.string.no),
|
||||||
},
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
cancelText = UiText.Resource(UiR.string.no)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.painterResource
|
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.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
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.ValidationViewModel
|
||||||
import com.meloda.app.fast.auth.validation.ValidationViewModelImpl
|
import com.meloda.app.fast.auth.validation.ValidationViewModelImpl
|
||||||
import com.meloda.app.fast.auth.validation.model.ValidationScreenState
|
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.MaterialDialog
|
||||||
import com.meloda.app.fast.designsystem.TextFieldErrorText
|
import com.meloda.app.fast.designsystem.TextFieldErrorText
|
||||||
import com.meloda.app.fast.designsystem.getString
|
import com.meloda.app.fast.designsystem.getString
|
||||||
@@ -96,7 +97,7 @@ fun ValidationScreen(
|
|||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
var confirmedExit by rememberSaveable {
|
var confirmedExit by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,14 +119,13 @@ fun ValidationScreen(
|
|||||||
|
|
||||||
if (showExitAlert) {
|
if (showExitAlert) {
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
onDismissAction = { showExitAlert = false },
|
onDismissRequest = { showExitAlert = false },
|
||||||
title = UiText.Simple("Confirmation"),
|
title = stringResource(id = UiR.string.warning_confirmation),
|
||||||
text = UiText.Simple("Are you sure? Authorization process will be cancelled."),
|
text = stringResource(id = UiR.string.validation_exit_warning),
|
||||||
confirmText = UiText.Resource(UiR.string.yes),
|
confirmAction = { confirmedExit = true },
|
||||||
confirmAction = {
|
confirmText = stringResource(id = UiR.string.yes),
|
||||||
confirmedExit = true
|
cancelText = stringResource(id = UiR.string.no),
|
||||||
},
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
cancelText = UiText.Resource(UiR.string.no)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -47,8 +47,7 @@ interface ConversationsViewModel {
|
|||||||
fun onPaginationConditionsMet()
|
fun onPaginationConditionsMet()
|
||||||
|
|
||||||
fun onDeleteDialogDismissed()
|
fun onDeleteDialogDismissed()
|
||||||
|
fun onDeleteDialogPositiveClick()
|
||||||
fun onDeleteDialogPositiveClick(conversationId: Int)
|
|
||||||
|
|
||||||
fun onRefresh()
|
fun onRefresh()
|
||||||
|
|
||||||
@@ -56,7 +55,8 @@ interface ConversationsViewModel {
|
|||||||
fun onConversationItemLongClick(conversation: UiConversation)
|
fun onConversationItemLongClick(conversation: UiConversation)
|
||||||
|
|
||||||
fun onPinDialogDismissed()
|
fun onPinDialogDismissed()
|
||||||
fun onPinDialogPositiveClick(conversation: UiConversation)
|
fun onPinDialogPositiveClick()
|
||||||
|
|
||||||
fun onOptionClicked(conversation: UiConversation, option: ConversationOption)
|
fun onOptionClicked(conversation: UiConversation, option: ConversationOption)
|
||||||
|
|
||||||
fun onErrorConsumed()
|
fun onErrorConsumed()
|
||||||
@@ -104,9 +104,11 @@ class ConversationsViewModelImpl(
|
|||||||
emitShowOptions { old -> old.copy(showDeleteDialog = null) }
|
emitShowOptions { old -> old.copy(showDeleteDialog = null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeleteDialogPositiveClick(conversationId: Int) {
|
override fun onDeleteDialogPositiveClick() {
|
||||||
|
val conversationId = screenState.value.showOptions.showDeleteDialog ?: return
|
||||||
deleteConversation(conversationId)
|
deleteConversation(conversationId)
|
||||||
hideOptions(conversationId)
|
hideOptions(conversationId)
|
||||||
|
onDeleteDialogDismissed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefresh() {
|
override fun onRefresh() {
|
||||||
@@ -168,9 +170,11 @@ class ConversationsViewModelImpl(
|
|||||||
emitShowOptions { old -> old.copy(showPinDialog = null) }
|
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)
|
pinConversation(conversation.id, !conversation.isPinned)
|
||||||
hideOptions(conversation.id)
|
hideOptions(conversation.id)
|
||||||
|
onPinDialogDismissed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionClicked(conversation: UiConversation, option: ConversationOption) {
|
override fun onOptionClicked(conversation: UiConversation, option: ConversationOption) {
|
||||||
@@ -222,7 +226,8 @@ class ConversationsViewModelImpl(
|
|||||||
private fun loadConversations(
|
private fun loadConversations(
|
||||||
offset: Int = currentOffset.value
|
offset: Int = currentOffset.value
|
||||||
) {
|
) {
|
||||||
conversationsUseCase.getConversations(count = LOAD_COUNT, offset = offset).listenValue { state ->
|
conversationsUseCase.getConversations(count = LOAD_COUNT, offset = offset)
|
||||||
|
.listenValue { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = { error ->
|
||||||
if (error is State.Error.ApiError) {
|
if (error is State.Error.ApiError) {
|
||||||
|
|||||||
+16
-59
@@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
@@ -68,7 +67,6 @@ import androidx.core.view.HapticFeedbackConstantsCompat
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.meloda.app.fast.common.UiText
|
|
||||||
import com.meloda.app.fast.conversations.ConversationsViewModel
|
import com.meloda.app.fast.conversations.ConversationsViewModel
|
||||||
import com.meloda.app.fast.conversations.ConversationsViewModelImpl
|
import com.meloda.app.fast.conversations.ConversationsViewModelImpl
|
||||||
import com.meloda.app.fast.conversations.model.ConversationOption
|
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.LocalTheme
|
||||||
import com.meloda.app.fast.designsystem.MaterialDialog
|
import com.meloda.app.fast.designsystem.MaterialDialog
|
||||||
import com.meloda.app.fast.designsystem.components.FullScreenLoader
|
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.model.BaseError
|
||||||
import com.meloda.app.fast.ui.ErrorView
|
import com.meloda.app.fast.ui.ErrorView
|
||||||
import dev.chrisbanes.haze.haze
|
import dev.chrisbanes.haze.haze
|
||||||
@@ -398,72 +397,30 @@ fun HandleDialogs(
|
|||||||
val showOptions = screenState.showOptions
|
val showOptions = screenState.showOptions
|
||||||
|
|
||||||
if (showOptions.showDeleteDialog != null) {
|
if (showOptions.showDeleteDialog != null) {
|
||||||
val conversationId = showOptions.showDeleteDialog
|
MaterialDialog(
|
||||||
DeleteDialog(
|
onDismissRequest = viewModel::onDeleteDialogDismissed,
|
||||||
conversationId = conversationId,
|
title = stringResource(id = UiR.string.confirm_delete_conversation),
|
||||||
viewModel = viewModel
|
confirmAction = viewModel::onDeleteDialogPositiveClick,
|
||||||
|
confirmText = stringResource(id = UiR.string.action_delete),
|
||||||
|
cancelText = stringResource(id = UiR.string.cancel)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
showOptions.showPinDialog?.let { conversation ->
|
if (showOptions.showPinDialog != null) {
|
||||||
PinDialog(
|
val conversation = showOptions.showPinDialog
|
||||||
conversation = conversation,
|
|
||||||
viewModel = viewModel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DeleteDialog(
|
|
||||||
conversationId: Int,
|
|
||||||
viewModel: ConversationsViewModel
|
|
||||||
) {
|
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
title = UiText.Resource(UiR.string.confirm_delete_conversation),
|
onDismissRequest = viewModel::onPinDialogDismissed,
|
||||||
confirmText = UiText.Resource(UiR.string.action_delete),
|
title = stringResource(
|
||||||
confirmAction = { viewModel.onDeleteDialogPositiveClick(conversationId) },
|
id = if (conversation.isPinned) UiR.string.confirm_unpin_conversation
|
||||||
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
|
else UiR.string.confirm_pin_conversation
|
||||||
),
|
),
|
||||||
confirmText = UiText.Resource(
|
confirmAction = viewModel::onPinDialogPositiveClick,
|
||||||
if (conversation.isPinned) UiR.string.action_unpin
|
confirmText = stringResource(
|
||||||
|
id = if (conversation.isPinned) UiR.string.action_unpin
|
||||||
else UiR.string.action_pin
|
else UiR.string.action_pin
|
||||||
),
|
),
|
||||||
confirmAction = {
|
cancelText = stringResource(id = UiR.string.cancel)
|
||||||
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?>
|
val isLongPollBackgroundEnabled: StateFlow<Boolean?>
|
||||||
|
|
||||||
fun onLogOutAlertDismissed()
|
fun onLogOutAlertDismissed()
|
||||||
|
|
||||||
fun onPerformCrashAlertDismissed()
|
|
||||||
|
|
||||||
fun onPerformCrashPositiveButtonClicked()
|
|
||||||
|
|
||||||
fun onLogOutAlertPositiveClick()
|
fun onLogOutAlertPositiveClick()
|
||||||
|
|
||||||
fun onLongPollingAlertPositiveClicked()
|
fun onPerformCrashAlertDismissed()
|
||||||
fun onLongPollingAlertDismissed()
|
fun onPerformCrashPositiveButtonClicked()
|
||||||
|
|
||||||
fun onSettingsItemClicked(key: String)
|
fun onSettingsItemClicked(key: String)
|
||||||
fun onSettingsItemLongClicked(key: String)
|
fun onSettingsItemLongClicked(key: String)
|
||||||
@@ -68,14 +63,6 @@ class SettingsViewModelImpl(
|
|||||||
emitShowOptions { old -> old.copy(showLogOut = false) }
|
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() {
|
override fun onLogOutAlertPositiveClick() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
accountsRepository.storeAccounts(
|
accountsRepository.storeAccounts(
|
||||||
@@ -93,18 +80,12 @@ class SettingsViewModelImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPollingAlertPositiveClicked() {
|
override fun onPerformCrashAlertDismissed() {
|
||||||
screenState.setValue { old -> old.copy(isNeedToRequestNotificationPermission = true) }
|
emitShowOptions { old -> old.copy(showPerformCrash = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPollingAlertDismissed() {
|
override fun onPerformCrashPositiveButtonClicked() {
|
||||||
screenState.setValue { old ->
|
throw Exception("Test exception")
|
||||||
old.copy(
|
|
||||||
showOptions = old.showOptions.copy(
|
|
||||||
showLongPollNotifications = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSettingsItemClicked(key: String) {
|
override fun onSettingsItemClicked(key: String) {
|
||||||
|
|||||||
-2
@@ -3,14 +3,12 @@ package com.meloda.app.fast.settings.model
|
|||||||
data class SettingsShowOptions(
|
data class SettingsShowOptions(
|
||||||
val showLogOut: Boolean,
|
val showLogOut: Boolean,
|
||||||
val showPerformCrash: Boolean,
|
val showPerformCrash: Boolean,
|
||||||
val showLongPollNotifications: Boolean
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY: SettingsShowOptions = SettingsShowOptions(
|
val EMPTY: SettingsShowOptions = SettingsShowOptions(
|
||||||
showLogOut = false,
|
showLogOut = false,
|
||||||
showPerformCrash = false,
|
showPerformCrash = false,
|
||||||
showLongPollNotifications = false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-49
@@ -34,11 +34,11 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.meloda.app.fast.common.UiText
|
|
||||||
import com.meloda.app.fast.common.UserConfig
|
import com.meloda.app.fast.common.UserConfig
|
||||||
import com.meloda.app.fast.datastore.SettingsKeys
|
import com.meloda.app.fast.datastore.SettingsKeys
|
||||||
import com.meloda.app.fast.datastore.UserSettings
|
import com.meloda.app.fast.datastore.UserSettings
|
||||||
import com.meloda.app.fast.datastore.isUsingDarkMode
|
import com.meloda.app.fast.datastore.isUsingDarkMode
|
||||||
|
import com.meloda.app.fast.designsystem.ActionInvokeDismiss
|
||||||
import com.meloda.app.fast.designsystem.LocalTheme
|
import com.meloda.app.fast.designsystem.LocalTheme
|
||||||
import com.meloda.app.fast.designsystem.MaterialDialog
|
import com.meloda.app.fast.designsystem.MaterialDialog
|
||||||
import com.meloda.app.fast.settings.HapticType
|
import com.meloda.app.fast.settings.HapticType
|
||||||
@@ -118,8 +118,6 @@ fun SettingsRoute(
|
|||||||
onLogOutButtonClicked()
|
onLogOutButtonClicked()
|
||||||
},
|
},
|
||||||
logoutDismissed = viewModel::onLogOutAlertDismissed,
|
logoutDismissed = viewModel::onLogOutAlertDismissed,
|
||||||
longPollingPositiveClick = viewModel::onLongPollingAlertPositiveClicked,
|
|
||||||
longPollingDismissed = viewModel::onLongPollingAlertDismissed,
|
|
||||||
screenState = screenState
|
screenState = screenState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -273,8 +271,6 @@ fun HandlePopups(
|
|||||||
performCrashDismissed: () -> Unit,
|
performCrashDismissed: () -> Unit,
|
||||||
logoutPositiveClick: () -> Unit,
|
logoutPositiveClick: () -> Unit,
|
||||||
logoutDismissed: () -> Unit,
|
logoutDismissed: () -> Unit,
|
||||||
longPollingPositiveClick: () -> Unit,
|
|
||||||
longPollingDismissed: () -> Unit,
|
|
||||||
screenState: SettingsScreenState
|
screenState: SettingsScreenState
|
||||||
) {
|
) {
|
||||||
val showOptions = screenState.showOptions
|
val showOptions = screenState.showOptions
|
||||||
@@ -290,12 +286,6 @@ fun HandlePopups(
|
|||||||
dismiss = logoutDismissed,
|
dismiss = logoutDismissed,
|
||||||
show = showOptions.showLogOut
|
show = showOptions.showLogOut
|
||||||
)
|
)
|
||||||
|
|
||||||
LongPollingNotificationsPermission(
|
|
||||||
positiveClick = longPollingPositiveClick,
|
|
||||||
dismiss = longPollingDismissed,
|
|
||||||
show = showOptions.showLongPollNotifications
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -306,12 +296,13 @@ fun PerformCrashDialog(
|
|||||||
) {
|
) {
|
||||||
if (show) {
|
if (show) {
|
||||||
MaterialDialog(
|
MaterialDialog(
|
||||||
title = UiText.Simple("Perform Crash"),
|
onDismissRequest = dismiss,
|
||||||
text = UiText.Simple("App will be crashed. Are you sure?"),
|
title = "Perform crash",
|
||||||
confirmText = UiText.Resource(UiR.string.yes),
|
text = "App will be crashed. Are you sure?",
|
||||||
confirmAction = positiveClick,
|
confirmAction = positiveClick,
|
||||||
cancelText = UiText.Resource(UiR.string.cancel),
|
confirmText = stringResource(id = UiR.string.yes),
|
||||||
onDismissAction = dismiss
|
cancelText = stringResource(id = UiR.string.cancel),
|
||||||
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,41 +316,20 @@ fun LogOutDialog(
|
|||||||
if (show) {
|
if (show) {
|
||||||
val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY
|
val isEasterEgg = UserConfig.userId == SettingsKeys.ID_DMITRY
|
||||||
|
|
||||||
val title = UiText.Resource(
|
MaterialDialog(
|
||||||
if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
onDismissRequest = dismiss,
|
||||||
|
title = stringResource(
|
||||||
|
id = if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
||||||
else UiR.string.sign_out_confirm_title
|
else UiR.string.sign_out_confirm_title
|
||||||
)
|
),
|
||||||
|
text = stringResource(id = UiR.string.sign_out_confirm),
|
||||||
val positiveText = UiText.Resource(
|
confirmAction = positiveClick,
|
||||||
if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
confirmText = stringResource(
|
||||||
|
id = if (isEasterEgg) UiR.string.easter_egg_log_out_dmitry
|
||||||
else UiR.string.action_sign_out
|
else UiR.string.action_sign_out
|
||||||
)
|
),
|
||||||
|
cancelText = stringResource(id = UiR.string.no),
|
||||||
MaterialDialog(
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
title = title,
|
|
||||||
text = UiText.Resource(UiR.string.sign_out_confirm),
|
|
||||||
confirmText = positiveText,
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user