Updated on 2026-08-14
This commit is contained in:
parent
6e642be3ca
commit
35898a31cd
8 changed files with 82 additions and 53 deletions
|
|
@ -27,7 +27,7 @@ internal class SendNotificationFactory(
|
|||
) {
|
||||
|
||||
fun create(): Flow<ImmutableList<SendNotification>> = currentStateProvider().currentState
|
||||
.filter { it == SendUiStateType.Send }
|
||||
.filter { it.type == SendUiStateType.Send }
|
||||
.map {
|
||||
val state = currentStateProvider()
|
||||
val feeState = state.feeState ?: return@map persistentListOf()
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ internal class SendStateFactory(
|
|||
// region UI states
|
||||
fun getInitialState(): SendUiState = SendUiState(
|
||||
clickIntents = clickIntents,
|
||||
currentState = MutableStateFlow(SendUiStateType.None),
|
||||
currentState = MutableStateFlow(SendUiCurrentScreen(type = SendUiStateType.None, isFromConfirmation = false)),
|
||||
event = consumedEvent(),
|
||||
isEditingDisabled = false,
|
||||
isBalanceHidden = false,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ internal data class SendUiState(
|
|||
val feeState: SendStates.FeeState? = null,
|
||||
val sendState: SendStates.SendState = SendStates.SendState(),
|
||||
val recipientList: MutableStateFlow<PagingData<SendRecipientListContent>> = MutableStateFlow(PagingData.empty()),
|
||||
val currentState: StateFlow<SendUiStateType>,
|
||||
val currentState: StateFlow<SendUiCurrentScreen>,
|
||||
val isBalanceHidden: Boolean,
|
||||
val event: StateEvent<SendEvent>,
|
||||
)
|
||||
|
|
@ -97,10 +97,16 @@ internal sealed class SendStates {
|
|||
val transactionDate: Long = 0L,
|
||||
val txUrl: String = "",
|
||||
val ignoreAmountReduce: Boolean = false,
|
||||
val isFromConfirmation: Boolean = true,
|
||||
val notifications: ImmutableList<SendNotification> = persistentListOf(),
|
||||
) : SendStates()
|
||||
}
|
||||
|
||||
data class SendUiCurrentScreen(
|
||||
val type: SendUiStateType,
|
||||
val isFromConfirmation: Boolean,
|
||||
)
|
||||
|
||||
enum class SendUiStateType {
|
||||
None,
|
||||
Amount,
|
||||
|
|
|
|||
|
|
@ -14,58 +14,65 @@ internal class StateRouter(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val isEditingDisabled: Boolean,
|
||||
) {
|
||||
private var mutableCurrentState: MutableStateFlow<SendUiStateType> = MutableStateFlow(
|
||||
private var mutableCurrentState: MutableStateFlow<SendUiCurrentScreen> = MutableStateFlow(
|
||||
if (isEditingDisabled) {
|
||||
SendUiStateType.None
|
||||
SendUiCurrentScreen(
|
||||
type = SendUiStateType.None,
|
||||
isFromConfirmation = false,
|
||||
)
|
||||
} else {
|
||||
SendUiStateType.Recipient
|
||||
SendUiCurrentScreen(
|
||||
type = SendUiStateType.Recipient,
|
||||
isFromConfirmation = false,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val currentState: StateFlow<SendUiStateType> = mutableCurrentState
|
||||
val currentState: StateFlow<SendUiCurrentScreen> = mutableCurrentState
|
||||
|
||||
fun popBackStack() {
|
||||
fragmentManager.get()?.popBackStack()
|
||||
}
|
||||
|
||||
fun onBackClick(isSuccess: Boolean = false) {
|
||||
val type = currentState.value.type
|
||||
when {
|
||||
isSuccess -> popBackStack()
|
||||
isEditingDisabled -> when (currentState.value) {
|
||||
isEditingDisabled -> when (type) {
|
||||
SendUiStateType.Send -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee))
|
||||
showFee()
|
||||
}
|
||||
else -> popBackStack()
|
||||
}
|
||||
else -> when (currentState.value) {
|
||||
else -> when (type) {
|
||||
SendUiStateType.Amount -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Address))
|
||||
showRecipient()
|
||||
continueToSend(::showRecipient)
|
||||
}
|
||||
SendUiStateType.Fee -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount))
|
||||
showAmount()
|
||||
continueToSend(::showAmount)
|
||||
}
|
||||
SendUiStateType.Send -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Fee))
|
||||
showFee()
|
||||
continueToSend(::showFee)
|
||||
}
|
||||
else -> popBackStack()
|
||||
else -> continueToSend(::popBackStack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNextClick(): SendUiStateType {
|
||||
val prevState = currentState.value
|
||||
when (currentState.value) {
|
||||
val prevState = currentState.value.type
|
||||
when (currentState.value.type) {
|
||||
SendUiStateType.Recipient -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Amount))
|
||||
showAmount()
|
||||
continueToSend(::showAmount)
|
||||
}
|
||||
SendUiStateType.Amount -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee))
|
||||
showFee()
|
||||
continueToSend(::showFee)
|
||||
}
|
||||
SendUiStateType.Fee -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.NextButtonClicked(SendScreenSource.Fee))
|
||||
|
|
@ -83,7 +90,7 @@ internal class StateRouter(
|
|||
if (isEditingDisabled) {
|
||||
popBackStack()
|
||||
} else {
|
||||
when (currentState.value) {
|
||||
when (currentState.value.type) {
|
||||
SendUiStateType.Amount -> {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.BackButtonClicked(SendScreenSource.Amount))
|
||||
showRecipient()
|
||||
|
|
@ -97,23 +104,27 @@ internal class StateRouter(
|
|||
}
|
||||
}
|
||||
|
||||
fun showAmount() {
|
||||
fun showAmount(isFromConfirmation: Boolean = false) {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.AmountScreenOpened)
|
||||
mutableCurrentState.update { SendUiStateType.Amount }
|
||||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Amount, isFromConfirmation) }
|
||||
}
|
||||
|
||||
fun showRecipient() {
|
||||
fun showRecipient(isFromConfirmation: Boolean = false) {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.AddressScreenOpened)
|
||||
mutableCurrentState.update { SendUiStateType.Recipient }
|
||||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Recipient, isFromConfirmation) }
|
||||
}
|
||||
|
||||
fun showFee() {
|
||||
fun showFee(isFromConfirmation: Boolean = false) {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.FeeScreenOpened)
|
||||
mutableCurrentState.update { SendUiStateType.Fee }
|
||||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Fee, isFromConfirmation) }
|
||||
}
|
||||
|
||||
private fun continueToSend(show: () -> Unit) {
|
||||
if (currentState.value.isFromConfirmation) showSend() else show()
|
||||
}
|
||||
|
||||
private fun showSend() {
|
||||
analyticsEventsHandler.send(SendAnalyticEvents.ConfirmationScreenOpened)
|
||||
mutableCurrentState.update { SendUiStateType.Send }
|
||||
mutableCurrentState.update { SendUiCurrentScreen(SendUiStateType.Send, isFromConfirmation = false) }
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ internal class FeeNotificationFactory(
|
|||
) {
|
||||
|
||||
fun create() = currentStateProvider().currentState
|
||||
.filter { it == SendUiStateType.Fee }
|
||||
.filter { it.type == SendUiStateType.Fee }
|
||||
.map {
|
||||
val state = currentStateProvider()
|
||||
val feeState = state.feeState ?: return@map persistentListOf()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -24,25 +23,29 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(uiState: SendUiState) {
|
||||
internal fun SendNavigationButtons(uiState: SendUiState, currentState: State<SendUiCurrentScreen>) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
SendSecondaryNavigationButton(uiState)
|
||||
SendSecondaryNavigationButton(
|
||||
uiState = uiState,
|
||||
currentState = currentState,
|
||||
)
|
||||
SendPrimaryNavigationButton(
|
||||
uiState = uiState,
|
||||
currentState = currentState,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
|
|
@ -51,12 +54,13 @@ internal fun SendNavigationButtons(uiState: SendUiState) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SendSecondaryNavigationButton(uiState: SendUiState) {
|
||||
val currentState = uiState.currentState.collectAsState()
|
||||
private fun SendSecondaryNavigationButton(uiState: SendUiState, currentState: State<SendUiCurrentScreen>) {
|
||||
val isEditingDisabled = uiState.isEditingDisabled
|
||||
val isCorrectScreen = currentState.value == SendUiStateType.Amount || currentState.value == SendUiStateType.Fee
|
||||
val isFromConfirmation = currentState.value.isFromConfirmation
|
||||
val isCorrectScreen =
|
||||
currentState.value.type == SendUiStateType.Amount || currentState.value.type == SendUiStateType.Fee
|
||||
AnimatedVisibility(
|
||||
visible = !isEditingDisabled && isCorrectScreen,
|
||||
visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation,
|
||||
enter = expandHorizontally(expandFrom = Alignment.End),
|
||||
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
|
||||
) {
|
||||
|
|
@ -77,8 +81,11 @@ private fun SendSecondaryNavigationButton(uiState: SendUiState) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier = Modifier) {
|
||||
val currentState = uiState.currentState.collectAsStateWithLifecycle()
|
||||
private fun SendPrimaryNavigationButton(
|
||||
uiState: SendUiState,
|
||||
currentState: State<SendUiCurrentScreen>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isSuccess = uiState.sendState.isSuccess
|
||||
val isSending = uiState.sendState.isSending
|
||||
val txUrl = uiState.sendState.txUrl
|
||||
|
|
@ -100,7 +107,7 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier
|
|||
modifier = modifier,
|
||||
) { textId ->
|
||||
when {
|
||||
currentState.value == SendUiStateType.Send && !isSuccess -> {
|
||||
currentState.value.type == SendUiStateType.Send && !isSuccess -> {
|
||||
val hapticFeedback = rememberHapticFeedback(state = currentState, onAction = buttonClick)
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(textId),
|
||||
|
|
@ -110,7 +117,7 @@ private fun SendPrimaryNavigationButton(uiState: SendUiState, modifier: Modifier
|
|||
showProgress = isSending,
|
||||
)
|
||||
}
|
||||
currentState.value == SendUiStateType.Send && isSuccess -> {
|
||||
currentState.value.type == SendUiStateType.Send && isSuccess -> {
|
||||
PrimaryButtonsDone(
|
||||
textRes = textId,
|
||||
txUrl = txUrl,
|
||||
|
|
@ -177,15 +184,19 @@ private fun PrimaryButtonsDone(
|
|||
|
||||
private fun getButtonData(
|
||||
uiState: SendUiState,
|
||||
currentState: State<SendUiStateType>,
|
||||
currentState: State<SendUiCurrentScreen>,
|
||||
isSuccess: Boolean,
|
||||
): Pair<Int, () -> Unit> {
|
||||
return when (currentState.value) {
|
||||
return when (currentState.value.type) {
|
||||
SendUiStateType.None,
|
||||
SendUiStateType.Amount,
|
||||
SendUiStateType.Recipient,
|
||||
SendUiStateType.Fee,
|
||||
-> R.string.common_next to uiState.clickIntents::onNextClick
|
||||
-> if (currentState.value.isFromConfirmation) {
|
||||
R.string.common_continue to uiState.clickIntents::onNextClick
|
||||
} else {
|
||||
R.string.common_next to uiState.clickIntents::onNextClick
|
||||
}
|
||||
SendUiStateType.Send -> if (isSuccess) {
|
||||
R.string.common_close
|
||||
} else {
|
||||
|
|
@ -194,8 +205,8 @@ private fun getButtonData(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isButtonEnabled(currentState: State<SendUiStateType>, uiState: SendUiState): Boolean {
|
||||
return when (currentState.value) {
|
||||
private fun isButtonEnabled(currentState: State<SendUiCurrentScreen>, uiState: SendUiState): Boolean {
|
||||
return when (currentState.value.type) {
|
||||
SendUiStateType.Amount -> uiState.amountState?.isPrimaryButtonEnabled ?: false
|
||||
SendUiStateType.Recipient -> uiState.recipientState?.isPrimaryButtonEnabled ?: false
|
||||
SendUiStateType.Fee -> uiState.feeState?.isPrimaryButtonEnabled ?: false
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.paging.compose.collectAsLazyPagingItems
|
|||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent
|
||||
|
|
@ -40,14 +41,14 @@ internal fun SendScreen(uiState: SendUiState) {
|
|||
.background(color = TangemTheme.colors.background.tertiary),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val titleRes = when (currentState.value) {
|
||||
val titleRes = when (currentState.value.type) {
|
||||
SendUiStateType.Amount -> R.string.send_amount_label
|
||||
SendUiStateType.Recipient -> R.string.send_recipient_label
|
||||
SendUiStateType.Fee -> R.string.common_fee_selector_title
|
||||
SendUiStateType.Send -> if (!isSuccess) R.string.send_confirm_label else null
|
||||
else -> null
|
||||
}
|
||||
val iconRes = if (currentState.value == SendUiStateType.Recipient) {
|
||||
val iconRes = if (currentState.value.type == SendUiStateType.Recipient) {
|
||||
R.drawable.ic_qrcode_scan_24
|
||||
} else {
|
||||
null
|
||||
|
|
@ -67,7 +68,7 @@ internal fun SendScreen(uiState: SendUiState) {
|
|||
modifier = Modifier
|
||||
.weight(1f),
|
||||
)
|
||||
SendNavigationButtons(uiState)
|
||||
SendNavigationButtons(uiState, currentState)
|
||||
}
|
||||
|
||||
SendEventEffect(
|
||||
|
|
@ -79,7 +80,7 @@ internal fun SendScreen(uiState: SendUiState) {
|
|||
@Composable
|
||||
private fun SendScreenContent(
|
||||
uiState: SendUiState,
|
||||
currentState: State<SendUiStateType>,
|
||||
currentState: State<SendUiCurrentScreen>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val recipientList = uiState.recipientList.collectAsLazyPagingItems()
|
||||
|
|
@ -88,7 +89,7 @@ private fun SendScreenContent(
|
|||
label = "Send Scree Navigation",
|
||||
modifier = modifier,
|
||||
) { state ->
|
||||
when (state) {
|
||||
when (state.type) {
|
||||
SendUiStateType.Amount -> SendAmountContent(
|
||||
amountState = uiState.amountState,
|
||||
isBalanceHiding = uiState.isBalanceHidden,
|
||||
|
|
|
|||
|
|
@ -377,8 +377,8 @@ internal class SendViewModel @Inject constructor(
|
|||
private fun onStateActive() {
|
||||
uiState.currentState
|
||||
.onEach {
|
||||
when (it) {
|
||||
SendUiStateType.Fee -> loadFee()
|
||||
when (it.type) {
|
||||
SendUiStateType.Fee -> if (!it.isFromConfirmation) loadFee()
|
||||
SendUiStateType.Send -> sendIdleTimer = System.currentTimeMillis()
|
||||
else -> Unit
|
||||
}
|
||||
|
|
@ -576,17 +576,17 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun showAmount() {
|
||||
stateRouter.showAmount()
|
||||
stateRouter.showAmount(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Amount))
|
||||
}
|
||||
|
||||
override fun showRecipient() {
|
||||
stateRouter.showRecipient()
|
||||
stateRouter.showRecipient(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Address))
|
||||
}
|
||||
|
||||
override fun showFee() {
|
||||
stateRouter.showFee()
|
||||
stateRouter.showFee(isFromConfirmation = true)
|
||||
analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue