Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-25 14:12:17 +05:00
parent e5c732e19d
commit adaa72f359
6 changed files with 184 additions and 52 deletions

View file

@ -28,9 +28,58 @@ internal data class SendUiState(
val recipientState: SendStates.RecipientState? = null,
val feeState: SendStates.FeeState? = null,
val sendState: SendStates.SendState? = null,
val editAmountState: SendStates.AmountState? = null,
val editRecipientState: SendStates.RecipientState? = null,
val editFeeState: SendStates.FeeState? = null,
val isBalanceHidden: Boolean,
val event: StateEvent<SendEvent>,
)
) {
fun getAmountState(isEditState: Boolean): SendStates.AmountState? {
return if (isEditState) {
editAmountState
} else {
amountState
}
}
fun getRecipientState(isEditState: Boolean): SendStates.RecipientState? {
return if (isEditState) {
editRecipientState
} else {
recipientState
}
}
fun getFeeState(isEditState: Boolean): SendStates.FeeState? {
return if (isEditState) {
editFeeState
} else {
feeState
}
}
fun copyWrapped(
isEditState: Boolean,
amountState: SendStates.AmountState? = this.amountState,
feeState: SendStates.FeeState? = this.feeState,
recipientState: SendStates.RecipientState? = this.recipientState,
sendState: SendStates.SendState? = this.sendState,
): SendUiState = if (isEditState) {
copy(
editAmountState = amountState,
editFeeState = feeState,
editRecipientState = recipientState,
)
} else {
copy(
amountState = amountState,
feeState = feeState,
recipientState = recipientState,
sendState = sendState,
)
}
}
@Stable
internal sealed class SendStates {
@ -105,6 +154,9 @@ enum class SendUiStateType {
None,
Recipient,
Amount,
Send,
Fee,
Send,
EditAmount,
EditRecipient,
EditFee,
}

View file

@ -18,6 +18,9 @@ internal class StateRouter(
val currentState: StateFlow<SendUiCurrentScreen>
get() = mutableCurrentState
val isEditState: Boolean
get() = currentState.value.isFromConfirmation
fun clear() {
mutableCurrentState.update { getInitState() }
}
@ -35,21 +38,29 @@ internal class StateRouter(
else -> popBackStack()
}
else -> when (type) {
SendUiStateType.Amount -> continueToSend(::showRecipient)
SendUiStateType.Amount -> showRecipient()
SendUiStateType.Fee -> showSend()
SendUiStateType.Send -> continueToSend(::showAmount)
else -> continueToSend(::popBackStack)
SendUiStateType.Send -> showAmount()
SendUiStateType.Recipient -> popBackStack()
SendUiStateType.EditAmount -> showSend()
SendUiStateType.EditRecipient -> showSend()
SendUiStateType.EditFee -> showSend()
else -> popBackStack()
}
}
}
fun onNextClick() {
when (currentState.value.type) {
SendUiStateType.Recipient -> continueToSend(::showAmount)
SendUiStateType.Recipient -> showAmount()
SendUiStateType.Amount,
SendUiStateType.Fee,
SendUiStateType.EditAmount,
SendUiStateType.EditRecipient,
SendUiStateType.EditFee,
-> showSend()
SendUiStateType.Send -> onBackClick()
else -> popBackStack()
}
}
@ -67,17 +78,35 @@ internal class StateRouter(
fun showAmount(isFromConfirmation: Boolean = false) {
analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened)
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Amount, isFromConfirmation) }
mutableCurrentState.update {
if (isFromConfirmation) {
SendUiCurrentScreen(SendUiStateType.EditAmount, true)
} else {
SendUiCurrentScreen(SendUiStateType.Amount, false)
}
}
}
fun showRecipient(isFromConfirmation: Boolean = false) {
analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened)
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Recipient, isFromConfirmation) }
mutableCurrentState.update {
if (isFromConfirmation) {
SendUiCurrentScreen(SendUiStateType.EditRecipient, true)
} else {
SendUiCurrentScreen(SendUiStateType.Recipient, false)
}
}
}
fun showFee(isFromConfirmation: Boolean = false) {
analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened)
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Fee, isFromConfirmation) }
mutableCurrentState.update {
if (isFromConfirmation) {
SendUiCurrentScreen(SendUiStateType.EditFee, true)
} else {
SendUiCurrentScreen(SendUiStateType.Fee, false)
}
}
}
fun showSend() {
@ -85,10 +114,6 @@ internal class StateRouter(
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Send, isFromConfirmation = false) }
}
private fun continueToSend(show: () -> Unit) {
if (currentState.value.isFromConfirmation) showSend() else show()
}
private fun getInitState() = if (isEditingDisabled) {
SendUiCurrentScreen(
type = SendUiStateType.None,

View file

@ -0,0 +1,26 @@
package com.tangem.features.send.impl.presentation.state.common
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
internal class SendSyncEditConverter(
private val currentStateProvider: Provider<SendUiState>,
) : Converter<Boolean, SendUiState> {
override fun convert(value: Boolean): SendUiState {
val state = currentStateProvider()
return if (value) {
state.copy(
amountState = state.editAmountState,
feeState = state.editFeeState,
recipientState = state.editRecipientState,
)
} else {
state.copy(
editAmountState = state.amountState,
editRecipientState = state.recipientState,
editFeeState = state.feeState,
)
}
}
}

View file

@ -14,7 +14,7 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onBackClick() {}
override fun onNextClick() {}
override fun onNextClick(isFromEdit: Boolean) {}
override fun onPrevClick() {}

View file

@ -1,8 +1,11 @@
package com.tangem.features.send.impl.presentation.ui
import androidx.activity.compose.BackHandler
import androidx.compose.animation.*
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.SnackbarHostState
@ -23,12 +26,13 @@ import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent
import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent
import com.tangem.features.send.impl.presentation.ui.send.SendContent
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.withIndex
@Composable
internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) {
val snackbarHostState = remember { SnackbarHostState() }
val sendState = uiState.sendState ?: return
BackHandler { uiState.clickIntents.onBackClick() }
Column(
modifier = Modifier
@ -38,38 +42,9 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen)
.background(color = TangemTheme.colors.background.tertiary),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val titleRes = when (currentState.type) {
SendUiStateType.Amount -> resourceReference(R.string.send_amount_label)
SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label)
SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title)
SendUiStateType.Send -> if (!sendState.isSuccess) {
resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName))
} else {
null
}
else -> null
}
val isSending = currentState.type == SendUiStateType.Send && !uiState.sendState.isSuccess
val subtitleRes = if (isSending) {
uiState.amountState?.walletName
} else {
null
}
val iconRes = if (currentState.type == SendUiStateType.Recipient) {
R.drawable.ic_qrcode_scan_24
} else {
null
}
AppBarWithBackButtonAndIcon(
text = titleRes?.resolveReference(),
subtitle = subtitleRes,
onBackClick = uiState.clickIntents::popBackStack,
onIconClick = uiState.clickIntents::onQrCodeScanClick,
backIconRes = R.drawable.ic_close_24,
iconRes = iconRes,
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.height(TangemTheme.dimens.size56),
SendAppBar(
uiState = uiState,
currentState = currentState,
)
SendScreenContent(
uiState = uiState,
@ -88,6 +63,50 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen)
)
}
@Composable
private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) {
val (titleRes, subtitleRes) = when (currentState.type) {
SendUiStateType.Amount,
SendUiStateType.EditAmount,
-> resourceReference(R.string.send_amount_label) to null
SendUiStateType.Recipient,
SendUiStateType.EditRecipient,
-> resourceReference(R.string.send_recipient_label) to null
SendUiStateType.Fee,
SendUiStateType.EditFee,
-> resourceReference(R.string.common_fee_selector_title) to null
SendUiStateType.Send -> if (uiState.sendState?.isSuccess == false) {
resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) to
uiState.amountState?.walletName
} else {
null to null
}
else -> null to null
}
val iconRes = if (currentState.type == SendUiStateType.Recipient) {
R.drawable.ic_qrcode_scan_24
} else {
null
}
val (backIcon, backClick) = when (currentState.type) {
SendUiStateType.EditAmount,
SendUiStateType.EditFee,
SendUiStateType.EditRecipient,
-> R.drawable.ic_back_24 to uiState.clickIntents::onBackClick
else -> R.drawable.ic_close_24 to uiState.clickIntents::popBackStack
}
AppBarWithBackButtonAndIcon(
text = titleRes?.resolveReference(),
subtitle = subtitleRes,
onBackClick = backClick,
onIconClick = uiState.clickIntents::onQrCodeScanClick,
backIconRes = backIcon,
iconRes = iconRes,
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.height(TangemTheme.dimens.size56),
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) {
@ -135,13 +154,23 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS
isBalanceHiding = uiState.isBalanceHidden,
clickIntents = uiState.clickIntents,
)
SendUiStateType.EditAmount -> SendAmountContent(
amountState = uiState.editAmountState,
isBalanceHiding = uiState.isBalanceHidden,
clickIntents = uiState.clickIntents,
)
SendUiStateType.Recipient -> SendRecipientContent(
uiState = uiState.recipientState,
clickIntents = uiState.clickIntents,
isBalanceHidden = uiState.isBalanceHidden,
)
SendUiStateType.Fee -> SendSpeedAndFeeContent(
state = uiState.feeState,
SendUiStateType.EditRecipient -> SendRecipientContent(
uiState = uiState.editRecipientState,
clickIntents = uiState.clickIntents,
isBalanceHidden = uiState.isBalanceHidden,
)
SendUiStateType.EditFee -> SendSpeedAndFeeContent(
state = uiState.editFeeState,
clickIntents = uiState.clickIntents,
)
SendUiStateType.Send -> SendContent(uiState)

View file

@ -14,7 +14,7 @@ internal interface SendClickIntents {
fun onBackClick()
fun onNextClick()
fun onNextClick(isFromEdit: Boolean = false)
fun onPrevClick()