From ce82e49ef4a58365e2f352ca56e4efd877497e83 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 12:15:22 +0500 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 +- .../usecase/GetFixedTxHistoryItemsUseCase.kt | 11 +++ .../domain/SendRecipientListContent.kt | 5 +- .../presentation/state/SendStateFactory.kt | 49 ++++++++-- ...t => SendRecipientHistoryListConverter.kt} | 49 +++------- .../recipient/SendRecipientStateConverter.kt | 6 +- .../SendRecipientWalletListConverter.kt | 39 ++++++++ .../state/recipient/utils/RecentListUtils.kt | 32 ++++++ .../ui/recipient/ListItemWithIcon.kt | 97 ++++++++++++++++++- .../ui/recipient/SendRecipientContent.kt | 58 +++++------ .../presentation/viewmodel/SendViewModel.kt | 66 ++++++------- 11 files changed, 294 insertions(+), 120 deletions(-) rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/{SendRecipientListConverter.kt => SendRecipientHistoryListConverter.kt} (71%) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1251772f83..9310a0153a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -422,7 +422,7 @@ Already included in the entered address Subtract Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee? - The commission amount is %@ times the recommended amount. Make sure that the custom settings are correct. + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s The transaction is not completed diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt index aa93bec246..8b5a463618 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt @@ -33,4 +33,15 @@ class GetFixedTxHistoryItemsUseCase( } }.mapLeft { TxHistoryListError.DataError(it) } } + + suspend fun getSync( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int = DEFAULT_PAGE_SIZE, + refresh: Boolean = false, + ): Either> { + return Either.catch { + repository.getFixedSizeTxHistoryItems(userWalletId, currency, pageSize, refresh) + }.mapLeft { TxHistoryListError.DataError(it) } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt index 0837f1f27c..03e9414c0a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt @@ -5,10 +5,11 @@ import com.tangem.core.ui.extensions.TextReference data class SendRecipientListContent( val id: String, - val title: TextReference, - val subtitle: TextReference, + val title: TextReference = TextReference.EMPTY, + val subtitle: TextReference = TextReference.EMPTY, val timestamp: TextReference? = null, val subtitleEndOffset: Int = 0, @DrawableRes val subtitleIconRes: Int? = null, val isVisible: Boolean = true, + val isLoading: Boolean = false, ) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 6861d18b0f..f3f304b7ff 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -4,6 +4,7 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -17,12 +18,14 @@ import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtrac import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter +import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter +import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import timber.log.Timber @Suppress("LongParameterList") @@ -77,9 +80,11 @@ internal class SendStateFactory( isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider, ) } - private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientListConverter( - currentStateProvider = currentStateProvider, + private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientWalletListConverter() + } + private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientHistoryListConverter( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } @@ -123,11 +128,23 @@ internal class SendStateFactory( //endregion //region recipient - fun onLoadedRecipientList(wallets: List, txHistory: List): SendUiState = - recipientListStateConverter.convert( - wallets = wallets, - txHistory = txHistory, + fun onLoadedWalletsList(wallets: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + wallets = recipientWalletListStateConverter.convert(wallets), + ), ) + } + + fun onLoadedHistoryList(txHistory: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + recent = recipientHistoryListStateConverter.convert(txHistory), + ), + ) + } fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() @@ -228,6 +245,22 @@ internal class SendStateFactory( ), ) } + + fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + val isNotValid = isAddressInWallet || !isValidAddress + return state.copy( + recipientState = recipientState.copy( + recent = recipientState.recent.map { recent -> + recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) + }.toPersistentList(), + wallets = recipientState.wallets.map { wallet -> + wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) + }.toPersistentList(), + ), + ) + } //endregion //region send diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt similarity index 71% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt index 046c7a153d..f6ccb6ba98 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt @@ -10,49 +10,26 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_DEFAULT_COUNT +import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_KEY_TAG +import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter import com.tangem.utils.toFormattedCurrencyString +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList -internal class SendRecipientListConverter( - private val currentStateProvider: Provider, +internal class SendRecipientHistoryListConverter( private val cryptoCurrencyStatusProvider: Provider, -) { +) : Converter, ImmutableList> { - fun convert(wallets: List, txHistory: List): SendUiState { + override fun convert(value: List): ImmutableList { val cryptoCurrency = cryptoCurrencyStatusProvider().currency - val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - - return state.copy( - recipientState = recipientState.copy( - wallets = wallets.filterWallets(), - recent = txHistory.filterRecipients(cryptoCurrency), - ), - ) - } - - private fun List.filterWallets() = this.filterNotNull() - .groupBy { item -> item.name } - .values.map { - it.mapIndexed { index, item -> - val name = if (it.size > 1) { - "${item.name} ${index.inc()}" - } else { - item.name - } - SendRecipientListContent( - id = item.address, - title = TextReference.Str(item.address), - subtitle = TextReference.Str(name), - ) - } + return value.filterRecipients(cryptoCurrency).ifEmpty { + emptyListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT) } - .flatten() - .toPersistentList() + } private fun List.filterRecipients(cryptoCurrency: CryptoCurrency) = this.filter { item -> val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer @@ -65,9 +42,9 @@ internal class SendRecipientListConverter( isTransfer && isSingleAddress && isNotContract } .take(RECENT_LIST_SIZE) - .map { tx -> + .mapIndexed { index, tx -> SendRecipientListContent( - id = tx.txHash, + id = "$RECENT_KEY_TAG$index", title = tx.extractAddress(), subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()), timestamp = tx.extractTimestamp(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt index dd7db52c45..9b3f770fcf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt @@ -2,10 +2,10 @@ package com.tangem.features.send.impl.presentation.state.recipient import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.features.send.impl.presentation.state.recipient.utils.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.persistentListOf internal class SendRecipientStateConverter( private val clickIntents: SendClickIntents, @@ -26,8 +26,8 @@ internal class SendRecipientStateConverter( memoTextField = memoFieldConverter.convertOrNull(value.memo), network = cryptoCurrencyStatusProvider().currency.network.name, isPrimaryButtonEnabled = false, - wallets = persistentListOf(), - recent = persistentListOf(), + wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT), + recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt new file mode 100644 index 0000000000..855e5d3a3b --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt @@ -0,0 +1,39 @@ +package com.tangem.features.send.impl.presentation.state.recipient + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.send.impl.presentation.domain.AvailableWallet +import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent +import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT +import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG +import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList + +internal class SendRecipientWalletListConverter : + Converter, PersistentList> { + override fun convert(value: List): PersistentList { + return value.filterWallets().ifEmpty { + emptyListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT) + } + } + + private fun List.filterWallets() = this.filterNotNull() + .groupBy { item -> item.name } + .values.map { + it.mapIndexed { index, item -> + val name = if (it.size > 1) { + "${item.name} ${index.inc()}" + } else { + item.name + } + SendRecipientListContent( + id = "${WALLET_KEY_TAG}$index", + title = TextReference.Str(item.address), + subtitle = TextReference.Str(name), + ) + } + } + .flatten() + .toPersistentList() +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt new file mode 100644 index 0000000000..c2d5ad9739 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt @@ -0,0 +1,32 @@ +package com.tangem.features.send.impl.presentation.state.recipient.utils + +import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent +import kotlinx.collections.immutable.toPersistentList + +internal const val WALLET_DEFAULT_COUNT = 1 +internal const val RECENT_DEFAULT_COUNT = 3 +internal const val WALLET_KEY_TAG = "wallet" +internal const val RECENT_KEY_TAG = "recent" + +internal fun loadingListState(tag: String, count: Int) = buildList { + repeat(count) { + add( + SendRecipientListContent( + id = "$tag$it", + isLoading = true, + ), + ) + } +}.toPersistentList() + +internal fun emptyListState(tag: String, count: Int) = buildList { + repeat(count) { + add( + SendRecipientListContent( + id = "$tag$it", + isLoading = false, + isVisible = false, + ), + ) + } +}.toPersistentList() \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt index c33885f290..9ef9f60a21 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt @@ -1,6 +1,10 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -9,6 +13,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource @@ -16,6 +21,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.icons.identicon.IdentIcon @@ -43,9 +50,42 @@ fun ListItemWithIcon( info: String? = null, subtitleEndOffset: Int = 0, @DrawableRes subtitleIconRes: Int? = null, + isLoading: Boolean = false, +) { + AnimatedContent( + targetState = isLoading, + label = "Recent List Content Animation", + transitionSpec = { fadeIn().togetherWith(fadeOut()) }, + ) { isLoadingState -> + if (isLoadingState) { + ListItemLoading(modifier = modifier) + } else { + ListItemWithIcon( + title = title, + subtitle = subtitle, + onClick = onClick, + info = info, + subtitleEndOffset = subtitleEndOffset, + subtitleIconRes = subtitleIconRes, + modifier = modifier, + ) + } + } +} + +@Composable +private fun ListItemWithIcon( + title: String, + subtitle: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + info: String? = null, + subtitleEndOffset: Int = 0, + @DrawableRes subtitleIconRes: Int? = null, ) { val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick) Row( + verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxWidth() .clickable { hapticFeedback() } @@ -55,13 +95,12 @@ fun ListItemWithIcon( address = title, modifier = Modifier .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size40) - .clip(RoundedCornerShape(TangemTheme.dimens.radius20)), + .size(TangemTheme.dimens.size36) + .clip(RoundedCornerShape(TangemTheme.dimens.radius18)), ) Column( - modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing10) - .padding(start = TangemTheme.dimens.spacing12), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, ) { EllipsisText( text = title, @@ -102,6 +141,43 @@ fun ListItemWithIcon( } } +@Composable +private fun ListItemLoading(modifier: Modifier = Modifier) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12), + ) { + CircleShimmer( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing8) + .size(TangemTheme.dimens.size36), + ) + Column( + modifier = Modifier + .height(TangemTheme.dimens.size32) + .padding(start = TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, + ) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + width = TangemTheme.dimens.spacing70, + height = TangemTheme.dimens.spacing12, + ), + ) + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + width = TangemTheme.dimens.spacing52, + height = TangemTheme.dimens.spacing12, + ), + ) + } + } +} + // region preview @Preview @Composable @@ -115,6 +191,7 @@ private fun ListItemWithIconPreview_Light( subtitleEndOffset = config.subtitleEndOffset, subtitleIconRes = config.iconRes, onClick = {}, + isLoading = config.isLoading, ) } } @@ -131,6 +208,7 @@ private fun ListItemWithIconPreview_Dark( subtitleEndOffset = config.subtitleEndOffset, subtitleIconRes = config.iconRes, onClick = {}, + isLoading = config.isLoading, ) } } @@ -141,6 +219,7 @@ private data class ListItemWithIconPreviewConfig( val info: String? = null, val subtitleEndOffset: Int = 0, val iconRes: Int? = null, + val isLoading: Boolean = false, ) private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvider( @@ -163,6 +242,14 @@ private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvid title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", subtitle = "Wallet", ), + ListItemWithIconPreviewConfig( + title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", + subtitle = "0.000000000000000000000000000000 BTC", + info = "0.0.0000 at 00:00", + subtitleEndOffset = "BTC".length, + iconRes = R.drawable.ic_arrow_down_24, + isLoading = true, + ), ), ) //endregion \ No newline at end of file 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 eb9b2b544a..f728ff97cb 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 @@ -2,8 +2,8 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.annotation.StringRes import androidx.compose.animation.* -import androidx.compose.foundation.ExperimentalFoundationApi 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 @@ -76,13 +76,13 @@ internal fun SendRecipientContent( listItem( list = wallets, clickIntents = clickIntents, - isLast = recipients.isEmpty(), + isLast = recipients.any { !it.isVisible }, isBalanceHidden = isBalanceHidden, ) listHeaderItem( titleRes = R.string.send_recent_transactions, isVisible = recipients.isNotEmpty() && recipients.first().isVisible, - isFirst = wallets.isEmpty(), + isFirst = wallets.any { !it.isVisible }, ) listItem( list = recipients, @@ -143,20 +143,9 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Boolean, isFirst: Boolean) { - item( - key = titleRes, - ) { - AnimatedVisibility( - visible = isVisible, - label = "Header Appearance Animation", - enter = slideInVertically() + fadeIn(), - exit = slideOutVertically() + fadeOut(), - modifier = Modifier - .animateItemPlacement() - .animateContentSize(), - ) { + item(key = titleRes) { + AnimateRecentAppearance(isVisible) { val (topPadding, paddingFromTop) = if (isFirst) { TangemTheme.dimens.spacing20 to TangemTheme.dimens.spacing12 } else { @@ -192,7 +181,6 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.listItem( list: ImmutableList, clickIntents: SendClickIntents, @@ -206,22 +194,20 @@ private fun LazyListScope.listItem( ) { index -> val item = list[index] val title = item.title.resolveReference() - AnimatedVisibility( - visible = item.isVisible, - label = "Header Appearance Animation", - enter = slideInVertically() + fadeIn(), - exit = slideOutVertically() + fadeOut(), - modifier = Modifier - .animateItemPlacement() - .animateContentSize(), - ) { + AnimateRecentAppearance(item.isVisible) { ListItemWithIcon( title = title, subtitle = if (isBalanceHidden) STARS else item.subtitle.resolveReference(), info = item.timestamp?.resolveReference(), subtitleEndOffset = item.subtitleEndOffset, subtitleIconRes = item.subtitleIconRes, - onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, + onClick = { + clickIntents.onRecipientAddressValueChange( + title, + EnterAddressSource.RecentAddress, + ) + }, + isLoading = item.isLoading, modifier = Modifier .then( if (isLast && index == list.lastIndex) { @@ -240,4 +226,22 @@ private fun LazyListScope.listItem( ) } } +} + +@Composable +private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable () -> Unit) { + AnimatedContent( + targetState = isVisible, + label = "Item Appearance Animation", + transitionSpec = { + (slideInHorizontally() + fadeIn()) + .togetherWith(slideOutVertically() + fadeOut()) + }, + ) { + if (it) { + content() + } else { + Box(modifier = Modifier.fillMaxWidth()) + } + } } \ No newline at end of file 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 c92d4b895d..2a3112371e 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 @@ -30,7 +30,6 @@ import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase -import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet @@ -56,7 +55,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber @@ -183,7 +181,6 @@ internal class SendViewModel @Inject constructor( private var balanceJobHolder = JobHolder() private var balanceHidingJobHolder = JobHolder() - private var recipientsJobHolder = JobHolder() private var feeJobHolder = JobHolder() private var addressValidationJobHolder = JobHolder() private var memoValidationJobHolder = JobHolder() @@ -374,34 +371,33 @@ internal class SendViewModel @Inject constructor( } private fun getWalletsAndRecent() { - combine( - flow = getUserWallets().conflate(), - flow2 = getTxHistory().conflate(), - ) { wallets, txHistory -> - uiState = stateFactory.onLoadedRecipientList( - wallets = wallets, - txHistory = txHistory, - ) + getUserWallets() + viewModelScope.launch(dispatchers.main) { + getTxHistory() } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(recipientsJobHolder) } - private fun getUserWallets(): Flow> { - return getWalletsUseCase() + private fun getUserWallets() { + getWalletsUseCase() + .conflate() .distinctUntilChanged() - .map { userWallets -> + .onEach { userWallets -> coroutineScope { - userWallets - .filterNot { it.walletId == userWalletId || it.isLocked } - .map { wallet -> - async(dispatchers.io) { - wallet.toAvailableWallet() - } - } - }.awaitAll() + runCatching { + userWallets + .filterNot { it.walletId == userWalletId || it.isLocked } + .map { wallet -> + async(dispatchers.io) { wallet.toAvailableWallet() } + }.awaitAll() + }.onSuccess { result -> + uiState = stateFactory.onLoadedWalletsList(wallets = result) + }.onFailure { + uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + } + } } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) } private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { @@ -431,14 +427,12 @@ internal class SendViewModel @Inject constructor( } } - private fun getTxHistory(): Flow> { - return getFixedTxHistoryItemsUseCase( + private suspend fun getTxHistory() { + val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( userWalletId = userWalletId, currency = cryptoCurrency, - ).fold( - ifRight = { it.distinctUntilChanged() }, - ifLeft = { emptyFlow() }, - ) + ).getOrElse { emptyList() } + uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList) } private fun onStateActive() { @@ -636,13 +630,9 @@ internal class SendViewModel @Inject constructor( } private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) { - val recipientState = uiState.recipientState ?: return - val isVisible = isAddressInWallet || !isValidAddress - uiState = uiState.copy( - recipientState = recipientState.copy( - recent = recipientState.recent.map { it.copy(isVisible = isVisible) }.toPersistentList(), - wallets = recipientState.wallets.map { it.copy(isVisible = isVisible) }.toPersistentList(), - ), + uiState = stateFactory.getHiddenRecentListState( + isAddressInWallet = isAddressInWallet, + isValidAddress = isValidAddress, ) }