Updated on 2026-08-14
This commit is contained in:
parent
b66f9fc50b
commit
0aa76e2aaa
8 changed files with 84 additions and 43 deletions
|
|
@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment.Companion.CenterEnd
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.components.inputrow.inner.PasteButton
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
|
|
@ -115,8 +116,18 @@ fun InputRowRecipient(
|
|||
|
||||
@Composable
|
||||
private fun RowScope.InputIcon(isLoading: Boolean, value: String) {
|
||||
var isLoadingProxy by remember { mutableStateOf(isLoading) }
|
||||
|
||||
// Do not show the progress indicator, which will disappear quickly
|
||||
LaunchedEffect(key1 = isLoading) {
|
||||
if (isLoading) {
|
||||
delay(timeMillis = 500)
|
||||
}
|
||||
isLoadingProxy = isLoading
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
targetState = isLoading,
|
||||
targetState = isLoadingProxy,
|
||||
label = "Indicator Show Change",
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -29,6 +29,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
|
|||
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 kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
internal fun SendNavigationButtons(
|
||||
|
|
@ -40,6 +41,7 @@ internal fun SendNavigationButtons(
|
|||
val isSuccess = sendState.isSuccess
|
||||
val isSendingState = currentState.type == SendUiStateType.Send && !isSuccess
|
||||
val isSentState = currentState.type == SendUiStateType.Send && isSuccess
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
|
|
@ -91,25 +93,26 @@ private fun SendNavigationButton(
|
|||
} else {
|
||||
TangemButtonIconPosition.None
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
modifier = modifier,
|
||||
) {
|
||||
|
||||
Row(modifier = modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = !isEditingDisabled && isCorrectScreen && !isFromConfirmation,
|
||||
enter = expandHorizontally(expandFrom = Alignment.End),
|
||||
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable { uiState.clickIntents.onPrevClick() }
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
Row {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable { uiState.clickIntents.onPrevClick() }
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
SpacerW12()
|
||||
}
|
||||
}
|
||||
TangemButton(
|
||||
text = stringResource(buttonTextId),
|
||||
|
|
@ -128,8 +131,18 @@ private fun SendNavigationButton(
|
|||
|
||||
@Composable
|
||||
private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modifier = Modifier) {
|
||||
var isVisibleProxy by remember { mutableStateOf(isVisible) }
|
||||
|
||||
// text appearance delay for smooth screen transitions
|
||||
LaunchedEffect(key1 = isVisible) {
|
||||
if (isVisible) {
|
||||
delay(timeMillis = 400)
|
||||
}
|
||||
isVisibleProxy = isVisible
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
visible = isVisibleProxy,
|
||||
modifier = modifier,
|
||||
enter = slideInVertically().plus(fadeIn()),
|
||||
exit = slideOutVertically().plus(fadeOut()),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.features.send.impl.presentation.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.*
|
||||
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
|
||||
|
|
@ -24,6 +22,8 @@ import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent
|
|||
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.*
|
||||
|
||||
@Composable
|
||||
internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) {
|
||||
|
|
@ -88,27 +88,46 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen)
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentScreen, modifier: Modifier = Modifier) {
|
||||
var lastState by remember { mutableIntStateOf(currentState.type.ordinal) }
|
||||
val direction = remember(currentState.type.ordinal) {
|
||||
if (lastState < currentState.type.ordinal) {
|
||||
AnimatedContentTransitionScope.SlideDirection.Start
|
||||
} else {
|
||||
AnimatedContentTransitionScope.SlideDirection.End
|
||||
}
|
||||
var currentStateProxy by remember { mutableStateOf(currentState) }
|
||||
var isTransitionAnimationRunning by remember { mutableStateOf(false) }
|
||||
|
||||
// Prevent quick screen changes to avoid some of the transition animation distortions
|
||||
LaunchedEffect(currentState) {
|
||||
snapshotFlow { isTransitionAnimationRunning }
|
||||
.withIndex()
|
||||
.map { (index, running) ->
|
||||
if (running && index != 0) {
|
||||
delay(timeMillis = 200)
|
||||
}
|
||||
running
|
||||
}
|
||||
.first { !it }
|
||||
|
||||
currentStateProxy = currentState
|
||||
}
|
||||
// Restrict pressing the back button while screen transition is running to avoid most of the animation distortions
|
||||
BackHandler(enabled = isTransitionAnimationRunning) {}
|
||||
|
||||
// Box is needed to fix animation with resizing of AnimatedContent
|
||||
Box(modifier = modifier) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
AnimatedContent(
|
||||
targetState = currentState,
|
||||
targetState = currentStateProxy,
|
||||
label = "Send Scree Navigation",
|
||||
transitionSpec = {
|
||||
lastState = currentState.type.ordinal
|
||||
val direction = if (initialState.type.ordinal < targetState.type.ordinal) {
|
||||
AnimatedContentTransitionScope.SlideDirection.Start
|
||||
} else {
|
||||
AnimatedContentTransitionScope.SlideDirection.End
|
||||
}
|
||||
|
||||
slideIntoContainer(towards = direction, animationSpec = tween())
|
||||
.togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween()))
|
||||
},
|
||||
) { state ->
|
||||
isTransitionAnimationRunning = transition.targetState != transition.currentState
|
||||
|
||||
when (state.type) {
|
||||
SendUiStateType.Amount -> SendAmountContent(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal fun SendAmountContent(
|
|||
clickIntents: SendClickIntents,
|
||||
) {
|
||||
if (amountState == null) return
|
||||
// Do not put fillMaxSize() in here
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.send.impl.presentation.ui.fee
|
|||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -27,8 +26,7 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
|
|||
val isCustomSelected = feeSendState?.selectedFee == FeeType.Custom
|
||||
val hasNotifications = notifications.isNotEmpty()
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
modifier = Modifier // Do not put fillMaxSize() in here
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -48,8 +47,7 @@ internal fun SendRecipientContent(
|
|||
val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } }
|
||||
val isError by remember(address.isError) { derivedStateOf { address.isError } }
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
modifier = Modifier // Do not put fillMaxSize() in here
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import androidx.compose.animation.*
|
|||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -33,9 +36,7 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L
|
|||
internal fun SendContent(uiState: SendUiState) {
|
||||
val sendState = uiState.sendState ?: return
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
blocks(uiState)
|
||||
tapHelp(isDisplay = sendState.showTapHelp)
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
|
|
@ -590,7 +590,7 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.getOnRecipientMemoValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue