diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt index c16d459a12..41bbc546b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowRecipient.kt @@ -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) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 604a9ba0a6..bf72bb774a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -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()), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index caf0a2f4f2..ffa3bf5d06 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -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( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index 6806468b2b..44b5a0244d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -21,6 +21,7 @@ internal fun SendAmountContent( clickIntents: SendClickIntents, ) { if (amountState == null) return + // Do not put fillMaxSize() in here LazyColumn( modifier = Modifier .padding( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index 88f4a37a85..4b6919dd3e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -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, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index f728ff97cb..e37a0ced3d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -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, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 78809756c4..6a6cc0550d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -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) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 90348ad8d5..d1aed8da05 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -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()