diff --git a/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt index 7ba9e05cff..7f0c9abe88 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TxHistoryDomainModule.kt @@ -3,6 +3,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.tokens.* import com.tangem.domain.txhistory.repository.TxHistoryRepository import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase +import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase import dagger.Module @@ -34,4 +35,10 @@ internal object TxHistoryDomainModule { ): GetExplorerTransactionUrlUseCase { return GetExplorerTransactionUrlUseCase(repository = txHistoryRepository) } + + @Provides + @ViewModelScoped + fun providesGetFixedTxHistoryItemsUseCase(txHistoryRepository: TxHistoryRepository): GetFixedTxHistoryItemsUseCase { + return GetFixedTxHistoryItemsUseCase(repository = txHistoryRepository) + } } \ No newline at end of file diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index 9120a85ef1..7b6663b564 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -10,11 +10,13 @@ import com.tangem.datasource.local.txhistory.TxHistoryItemsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network +import com.tangem.domain.txhistory.models.Page import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryState import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.txhistory.repository.TxHistoryRepository import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.walletmanager.utils.SdkPageConverter import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -25,6 +27,7 @@ class DefaultTxHistoryRepository( private val userWalletsStore: UserWalletsStore, private val txHistoryItemsStore: TxHistoryItemsStore, ) : TxHistoryRepository { + private val sdkPageConverter by lazy { SdkPageConverter() } override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, currency: CryptoCurrency): Int { val userWallet = getUserWallet(userWalletId) @@ -74,6 +77,43 @@ class DefaultTxHistoryRepository( } } + override suspend fun getFixedSizeTxHistoryItems( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int, + refresh: Boolean, + ): List { + cacheRegistry.invokeOnExpire( + key = getTxHistoryPageKey(currency, userWalletId, Page.Initial), + skipCache = refresh, + block = { fetchFixedSizeTxHistoryItems(userWalletId, currency, pageSize) }, + ) + val txs = txHistoryItemsStore.getSyncOrNull( + key = TxHistoryItemsStore.Key(userWalletId, currency), + page = Page.Initial, + )?.items + return txs ?: emptyList() + } + + private fun getTxHistoryPageKey(currency: CryptoCurrency, userWalletId: UserWalletId, page: Page): String { + return "tx_history_page_${currency}_${userWalletId}_$page" + } + + private suspend fun fetchFixedSizeTxHistoryItems( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int, + ) { + val wrappedItems = walletManagersFacade.getTxHistoryItems( + userWalletId = userWalletId, + currency = currency, + page = sdkPageConverter.convertBack(Page.Initial), + pageSize = pageSize, + ) + + txHistoryItemsStore.store(TxHistoryItemsStore.Key(userWalletId, currency), wrappedItems) + } + private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet { return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find user wallet with provided ID: $userWalletId" diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt index 381d469f98..b010a54db4 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt @@ -23,4 +23,12 @@ interface TxHistoryRepository { ): Flow> fun getTxExploreUrl(txHash: String, networkId: Network.ID): String + + @Throws(TxHistoryListError::class) + suspend fun getFixedSizeTxHistoryItems( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int, + refresh: Boolean, + ): List } \ No newline at end of file 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 new file mode 100644 index 0000000000..aa93bec246 --- /dev/null +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt @@ -0,0 +1,36 @@ +package com.tangem.domain.txhistory.usecase + +import arrow.core.Either +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.txhistory.models.TxHistoryItem +import com.tangem.domain.txhistory.models.TxHistoryListError +import com.tangem.domain.txhistory.repository.TxHistoryRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** + * Returns first page with size [DEFAULT_PAGE_SIZE] of tx history. + * Page size is preserved to reuse cached data in Token Details Screen. + * + * IMPORTANT!!! + * If page size bigger than [DEFAULT_PAGE_SIZE] is needed consider to implement another use case + * without use of cached data or increase [DEFAULT_PAGE_SIZE] + */ +class GetFixedTxHistoryItemsUseCase( + private val repository: TxHistoryRepository, +) { + + operator fun invoke( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int = DEFAULT_PAGE_SIZE, + refresh: Boolean = false, + ): Either>> { + return Either.catch { + flow { + emit(repository.getFixedSizeTxHistoryItems(userWalletId, currency, pageSize, refresh)) + } + }.mapLeft { TxHistoryListError.DataError(it) } + } +} \ No newline at end of file diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt index e5570e8077..cb6724fc60 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetTxHistoryItemsUseCase.kt @@ -11,7 +11,7 @@ import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch -private const val DEFAULT_PAGE_SIZE = 50 +const val DEFAULT_PAGE_SIZE = 50 // TODO: Add tests class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index 1189a49843..539fd6d734 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -73,7 +73,7 @@ internal class SendFragment : ComposeFragment() { SystemBarsEffect { setSystemBarsColor(systemBarsColor) } - SendScreen(viewModel.uiState) + SendScreen(viewModel.uiState, viewModel.stateRouter.currentState) } override fun onDestroy() { 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 47761c5956..0837f1f27c 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 @@ -1,23 +1,14 @@ package com.tangem.features.send.impl.presentation.domain import androidx.annotation.DrawableRes -import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference -import kotlinx.collections.immutable.PersistentList -@Immutable -internal sealed class SendRecipientListContent { - data class Item( - val id: String, - val title: TextReference, - val subtitle: TextReference, - val timestamp: TextReference? = null, - val subtitleEndOffset: Int = 0, - @DrawableRes val subtitleIconRes: Int? = null, - ) : SendRecipientListContent() - - data class Wallets( - val list: PersistentList, - val isWalletsOnly: Boolean, - ) : SendRecipientListContent() -} \ No newline at end of file +data class SendRecipientListContent( + val id: String, + val title: TextReference, + val subtitle: TextReference, + val timestamp: TextReference? = null, + val subtitleEndOffset: Int = 0, + @DrawableRes val subtitleIconRes: Int? = null, + val isVisible: Boolean = true, +) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt index 6f3021a2b5..b253266100 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt @@ -17,16 +17,18 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal +@Suppress("LongParameterList") internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, private val currencyChecksRepository: CurrencyChecksRepository, + private val stateRouterProvider: Provider, private val clickIntents: SendClickIntents, ) { - fun create(): Flow> = currentStateProvider().currentState + fun create(): Flow> = stateRouterProvider().currentState .filter { it.type == SendUiStateType.Send } .map { val state = currentStateProvider() 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 10b562c4c5..619ec36aab 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 @@ -1,6 +1,5 @@ package com.tangem.features.send.impl.presentation.state -import androidx.paging.PagingData import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter @@ -23,7 +22,6 @@ 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.coroutines.flow.MutableStateFlow import timber.log.Timber @Suppress("LongParameterList") @@ -78,7 +76,6 @@ internal class SendStateFactory( // region UI states fun getInitialState(): SendUiState = SendUiState( clickIntents = clickIntents, - currentState = MutableStateFlow(SendUiCurrentScreen(type = SendUiStateType.None, isFromConfirmation = false)), event = consumedEvent(), isEditingDisabled = false, isBalanceHidden = false, @@ -109,17 +106,11 @@ internal class SendStateFactory( //endregion //region recipient - fun onLoadedRecipientList( - wallets: List, - txHistory: PagingData, - txHistoryCount: Int, - ) { + fun onLoadedRecipientList(wallets: List, txHistory: List): SendUiState = recipientListStateConverter.convert( wallets = wallets, txHistory = txHistory, - txHistoryCount = txHistoryCount, ) - } fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index 8611ded493..34e2ddd3fe 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -2,7 +2,6 @@ package com.tangem.features.send.impl.presentation.state import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.paging.PagingData import com.tangem.blockchain.common.transaction.Fee import com.tangem.core.ui.components.currency.tokenicon.TokenIconState import com.tangem.core.ui.event.StateEvent @@ -17,8 +16,6 @@ import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import java.math.BigDecimal /** @@ -32,8 +29,6 @@ internal data class SendUiState( val recipientState: SendStates.RecipientState? = null, val feeState: SendStates.FeeState? = null, val sendState: SendStates.SendState = SendStates.SendState(), - val recipientList: MutableStateFlow> = MutableStateFlow(PagingData.empty()), - val currentState: StateFlow, val isBalanceHidden: Boolean, val event: StateEvent, ) @@ -64,7 +59,8 @@ internal sealed class SendStates { override val isPrimaryButtonEnabled: Boolean, val addressTextField: SendTextField.RecipientAddress, val memoTextField: SendTextField.RecipientMemo?, - val recipients: MutableStateFlow> = MutableStateFlow(PagingData.empty()), + val recent: ImmutableList, + val wallets: ImmutableList, val network: String, val isValidating: Boolean = false, ) : SendStates() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt index a9c6451ab9..fea5777945 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/StateRouter.kt @@ -28,7 +28,8 @@ internal class StateRouter( }, ) - val currentState: StateFlow = mutableCurrentState + val currentState: StateFlow + get() = mutableCurrentState fun popBackStack() { fragmentManager.get()?.popBackStack() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt index 47b85049a3..f8c304748b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt @@ -11,6 +11,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.R 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.state.StateRouter import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider @@ -21,16 +22,18 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal +@Suppress("LongParameterList") internal class FeeNotificationFactory( private val coinCryptoCurrencyStatusProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, + private val stateRouterProvider: Provider, private val clickIntents: SendClickIntents, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, ) { - fun create() = currentStateProvider().currentState + fun create() = stateRouterProvider().currentState .filter { it.type == SendUiStateType.Fee } .map { val state = currentStateProvider() 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/SendRecipientListConverter.kt index 0734f7a243..046c7a153d 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/SendRecipientListConverter.kt @@ -1,6 +1,5 @@ package com.tangem.features.send.impl.presentation.state.recipient -import androidx.paging.* import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList @@ -17,77 +16,66 @@ import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.utils.Provider import com.tangem.utils.toFormattedCurrencyString import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.flow.update internal class SendRecipientListConverter( private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, ) { - fun convert(wallets: List, txHistory: PagingData, txHistoryCount: Int) { - val filteredWallets = wallets.filterNotNull() - .groupBy { item -> item.name } - .values.flatten() - .mapIndexed { index, item -> - item.copy( - name = "${item.name} ${index.inc()}", - ) - } - - val walletsItem = getWalletItems(filteredWallets, txHistoryCount) - + fun convert(wallets: List, txHistory: List): SendUiState { val cryptoCurrency = cryptoCurrencyStatusProvider().currency - currentStateProvider().recipientList.update { - if (txHistoryCount == 0) { - PagingData.from(listOf(walletsItem)) - } else { - txHistory.filter { item -> - val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer - val isNotContract = item.interactionAddressType is TxHistoryItem.InteractionAddressType.User - val isSingleAddress = if (item.isOutgoing) { - item.destinationType is TxHistoryItem.DestinationType.Single - } else { - item.sourceType is TxHistoryItem.SourceType.Single - } - isTransfer && isSingleAddress && isNotContract - }.map { tx -> - SendRecipientListContent.Item( - id = tx.txHash, - title = tx.extractAddress(), - subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()), - timestamp = tx.extractTimestamp(), - subtitleEndOffset = cryptoCurrency.symbol.length, - subtitleIconRes = tx.extractIconRes(), - ) - }.insertWallets(walletsItem) - } - } - } + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state - private fun PagingData.insertWallets( - wallets: SendRecipientListContent.Wallets, - ): PagingData { - return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after -> - return@insertSeparators when { - before == null && after is SendRecipientListContent.Item -> wallets - else -> null - } - } - } - - private fun getWalletItems(wallets: List, txHistoryCount: Int): SendRecipientListContent.Wallets { - return SendRecipientListContent.Wallets( - wallets.map { - SendRecipientListContent.Item( - id = it.address, - title = TextReference.Str(it.address), - subtitle = TextReference.Str(it.name), - ) - }.toPersistentList(), - isWalletsOnly = txHistoryCount == 0, + 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), + ) + } + } + .flatten() + .toPersistentList() + + private fun List.filterRecipients(cryptoCurrency: CryptoCurrency) = this.filter { item -> + val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer + val isNotContract = item.interactionAddressType is TxHistoryItem.InteractionAddressType.User + val isSingleAddress = if (item.isOutgoing) { + item.destinationType is TxHistoryItem.DestinationType.Single + } else { + item.sourceType is TxHistoryItem.SourceType.Single + } + isTransfer && isSingleAddress && isNotContract + } + .take(RECENT_LIST_SIZE) + .map { tx -> + SendRecipientListContent( + id = tx.txHash, + title = tx.extractAddress(), + subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()), + timestamp = tx.extractTimestamp(), + subtitleEndOffset = cryptoCurrency.symbol.length, + subtitleIconRes = tx.extractIconRes(), + ) + }.toPersistentList() + private fun TxHistoryItem.extractAddress(): TextReference = if (isOutgoing) { when (val destination = destinationType) { is TxHistoryItem.DestinationType.Multiple -> TextReference.Res( @@ -122,4 +110,8 @@ internal class SendRecipientListConverter( val time = timestampInMillis.toTimeFormat() return TextReference.Res(R.string.send_date_format, wrappedList(date, time)) } + + companion object { + private const val RECENT_LIST_SIZE = 10 + } } \ No newline at end of file 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 a558d4ded6..ae375c97e2 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 @@ -5,6 +5,7 @@ import com.tangem.features.send.impl.presentation.state.SendStates 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, @@ -25,6 +26,8 @@ internal class SendRecipientStateConverter( memoTextField = memoFieldConverter.convertOrNull(), network = cryptoCurrencyStatusProvider().currency.network.name, isPrimaryButtonEnabled = false, + wallets = persistentListOf(), + recent = persistentListOf(), ) } } \ No newline at end of file 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 6fe98a42ac..2af0c26442 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 @@ -121,7 +121,7 @@ private fun SendPrimaryNavigationButton( PrimaryButtonsDone( textRes = textId, txUrl = txUrl, - onExploreClick = { uiState.clickIntents.onExploreClick(txUrl) }, + onExploreClick = uiState.clickIntents::onExploreClick, onShareClick = uiState.clickIntents::onShareClick, onDoneClick = buttonClick, modifier = Modifier, 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 6e862d9e07..2e0a451ec3 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 @@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle -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 @@ -26,11 +25,11 @@ 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.flow.StateFlow @Composable -internal fun SendScreen(uiState: SendUiState) { - val currentState = uiState.currentState.collectAsStateWithLifecycle() - val isSuccess = uiState.sendState.isSuccess +internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow) { + val currentState = currentStateFlow.collectAsStateWithLifecycle() val snackbarHostState = remember { SnackbarHostState() } BackHandler { uiState.clickIntents.onBackClick() } Column( @@ -45,7 +44,7 @@ internal fun SendScreen(uiState: SendUiState) { 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 + SendUiStateType.Send -> if (!uiState.sendState.isSuccess) R.string.send_confirm_label else null else -> null } val iconRes = if (currentState.value.type == SendUiStateType.Recipient) { @@ -83,7 +82,6 @@ private fun SendScreenContent( currentState: State, modifier: Modifier = Modifier, ) { - val recipientList = uiState.recipientList.collectAsLazyPagingItems() AnimatedContent( targetState = currentState.value, label = "Send Scree Navigation", @@ -98,7 +96,6 @@ private fun SendScreenContent( SendUiStateType.Recipient -> SendRecipientContent( uiState = uiState.recipientState, clickIntents = uiState.clickIntents, - recipientList = recipientList, ) SendUiStateType.Fee -> SendSpeedAndFeeContent( state = uiState.feeState, 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 d16846ae48..c33885f290 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 @@ -3,9 +3,7 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon @@ -18,9 +16,6 @@ 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 androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import androidx.constraintlayout.compose.Visibility 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 @@ -39,7 +34,6 @@ import com.tangem.features.send.impl.R * @param subtitleEndOffset offset for subtitle ellipsis * @param subtitleIconRes icon */ -@Suppress("DestructuringDeclarationWithTooManyEntries", "LongMethod") @Composable fun ListItemWithIcon( title: String, @@ -51,80 +45,60 @@ fun ListItemWithIcon( @DrawableRes subtitleIconRes: Int? = null, ) { val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick) - ConstraintLayout( + Row( modifier = modifier .fillMaxWidth() .clickable { hapticFeedback() } .padding(horizontal = TangemTheme.dimens.spacing12), ) { - val (iconRef, titleRef, subtitleRef, subtitleIconRef) = createRefs() - - val spacing2 = TangemTheme.dimens.spacing2 - val spacing8 = TangemTheme.dimens.spacing8 - val spacing10 = TangemTheme.dimens.spacing10 - val spacing12 = TangemTheme.dimens.spacing12 IdentIcon( address = title, modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing8) .size(TangemTheme.dimens.size40) - .clip(RoundedCornerShape(TangemTheme.dimens.radius20)) - .constrainAs(iconRef) { - start.linkTo(parent.start) - top.linkTo(parent.top, margin = spacing8) - bottom.linkTo(parent.bottom, margin = spacing8) - }, + .clip(RoundedCornerShape(TangemTheme.dimens.radius20)), ) - EllipsisText( - text = title, - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Justify, - ellipsis = TextEllipsis.Middle, + Column( modifier = Modifier - .constrainAs(titleRef) { - start.linkTo(iconRef.end, margin = spacing12) - end.linkTo(parent.end) - top.linkTo(parent.top, margin = spacing10) - width = Dimension.fillToConstraints - }, - ) - Icon( - painter = painterResource(id = subtitleIconRes ?: R.drawable.ic_arrow_down_24), - contentDescription = null, - tint = TangemTheme.colors.icon.informative, - modifier = Modifier - .size(TangemTheme.dimens.size16) - .background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f), CircleShape) - .constrainAs(subtitleIconRef) { - start.linkTo(iconRef.end, margin = spacing12) - top.linkTo(titleRef.bottom) - bottom.linkTo(parent.bottom, margin = spacing10) - visibility = if (subtitleIconRes == null) Visibility.Gone else Visibility.Visible - }, - ) - - val (text, offset) = remember(subtitle, info) { - if (info != null) { - val suffix = ", $info" - subtitle + suffix to suffix.length + subtitleEndOffset - } else { - subtitle to 0 + .padding(vertical = TangemTheme.dimens.spacing10) + .padding(start = TangemTheme.dimens.spacing12), + ) { + EllipsisText( + text = title, + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Justify, + ellipsis = TextEllipsis.Middle, + modifier = Modifier, + ) + Row { + if (subtitleIconRes != null) { + Icon( + painter = painterResource(id = subtitleIconRes), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + modifier = Modifier + .size(TangemTheme.dimens.size16) + .background(TangemTheme.colors.background.tertiary, CircleShape), + ) + } + val (text, offset) = remember(subtitle, info) { + if (info != null) { + val suffix = ", $info" + subtitle + suffix to suffix.length + subtitleEndOffset + } else { + subtitle to 0 + } + } + EllipsisText( + text = text, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ellipsis = TextEllipsis.OffsetEnd(offsetEnd = offset), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing2), + ) } } - EllipsisText( - text = text, - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - ellipsis = TextEllipsis.OffsetEnd(offsetEnd = offset), - modifier = Modifier - .constrainAs(subtitleRef) { - start.linkTo(subtitleIconRef.end, margin = spacing2, goneMargin = spacing12) - end.linkTo(parent.end) - top.linkTo(titleRef.bottom) - bottom.linkTo(parent.bottom, margin = spacing10) - width = Dimension.fillToConstraints - }, - ) } } 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 c80581a8f8..4c490fb1bc 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 @@ -1,8 +1,9 @@ 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.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -17,9 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.itemContentType -import androidx.paging.compose.itemKey import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @@ -29,18 +27,17 @@ import com.tangem.features.send.impl.presentation.domain.SendRecipientListConten import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import kotlinx.collections.immutable.ImmutableList private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY" private const val MEMO_FIELD_KEY = "MEMO_FIELD_KEY" -private const val MY_WALLETS_HEADER_KEY = "MY_WALLETS_HEADER_KEY" @Composable -internal fun SendRecipientContent( - uiState: SendStates.RecipientState?, - clickIntents: SendClickIntents, - recipientList: LazyPagingItems, -) { +internal fun SendRecipientContent(uiState: SendStates.RecipientState?, clickIntents: SendClickIntents) { if (uiState == null) return + val recipients = uiState.recent + val wallets = uiState.wallets + val memoField = uiState.memoTextField val address = uiState.addressTextField val isValidating by remember(uiState.isValidating) { derivedStateOf { uiState.isValidating } } val isError by remember(address.isError) { derivedStateOf { address.isError } } @@ -72,7 +69,7 @@ internal fun SendRecipientContent( ) } } - uiState.memoTextField?.let { memoField -> + if (memoField != null) { item(key = MEMO_FIELD_KEY) { val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText TextFieldWithPaste( @@ -89,144 +86,115 @@ internal fun SendRecipientContent( ) } } - recipientListItem( - recipientList = recipientList, - clickIntents = clickIntents, + listHeaderItem( + titleRes = R.string.send_recipient_wallets_title, + isVisible = wallets.isNotEmpty() && wallets.first().isVisible, + isFirst = true, ) + listItem(wallets, clickIntents, isLast = recipients.isEmpty()) + listHeaderItem( + titleRes = R.string.send_recent_transactions, + isVisible = recipients.isNotEmpty() && recipients.first().isVisible, + isFirst = wallets.isEmpty(), + ) + listItem(recipients, clickIntents, isLast = true) } } -@Suppress("LongMethod") @OptIn(ExperimentalFoundationApi::class) -private fun LazyListScope.recipientListItem( - recipientList: LazyPagingItems, +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(), + ) { + val (topPadding, paddingFromTop) = if (isFirst) { + TangemTheme.dimens.spacing20 to TangemTheme.dimens.spacing12 + } else { + TangemTheme.dimens.spacing0 to TangemTheme.dimens.spacing8 + } + val topRadius = if (isFirst) { + TangemTheme.dimens.radius12 + } else { + TangemTheme.dimens.radius0 + } + Text( + text = stringResource(titleRes), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .fillMaxWidth() + .padding(top = topPadding) + .clip( + RoundedCornerShape( + topEnd = topRadius, + topStart = topRadius, + ), + ) + .background(TangemTheme.colors.background.action) + .padding( + top = paddingFromTop, + bottom = TangemTheme.dimens.spacing8, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +private fun LazyListScope.listItem( + list: ImmutableList, clickIntents: SendClickIntents, + isLast: Boolean, ) { items( - count = recipientList.itemCount, - key = recipientList.itemKey { - when (it) { - is SendRecipientListContent.Wallets -> MY_WALLETS_HEADER_KEY - is SendRecipientListContent.Item -> it.id - } - }, - contentType = recipientList.itemContentType { it::class.java }, + count = list.size, + key = { list[it].id }, + contentType = { list[it]::class.java }, ) { index -> - recipientList[index]?.let { item -> - when (item) { - is SendRecipientListContent.Wallets -> { - RecipientWalletListItem( - item = item, - clickIntents = clickIntents, - modifier = Modifier - .animateItemPlacement() - .padding(top = TangemTheme.dimens.spacing20) - .then( - if (index == 0) { - val bottomRadius = if (item.isWalletsOnly) { - TangemTheme.dimens.radius12 - } else { - TangemTheme.dimens.radius0 - } - Modifier.clip( - RoundedCornerShape( - topEnd = TangemTheme.dimens.radius12, - topStart = TangemTheme.dimens.radius12, - bottomStart = bottomRadius, - bottomEnd = bottomRadius, - ), - ) - } else { - Modifier - }, - ), - ) - } - is SendRecipientListContent.Item -> { - val title = item.title.resolveReference() - ListItemWithIcon( - title = item.title.resolveReference(), - subtitle = item.subtitle.resolveReference(), - info = item.timestamp?.resolveReference(), - subtitleEndOffset = item.subtitleEndOffset, - subtitleIconRes = item.subtitleIconRes, - modifier = Modifier - .then( - if (index == recipientList.itemCount - 1) { - Modifier - .padding(bottom = TangemTheme.dimens.spacing20) - .clip( - RoundedCornerShape( - bottomEnd = TangemTheme.dimens.radius12, - bottomStart = TangemTheme.dimens.radius12, - ), - ) - } else { - Modifier - }, - ) - .background(TangemTheme.colors.background.action), - onClick = { - clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) + 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(), + ) { + ListItemWithIcon( + title = title, + subtitle = item.subtitle.resolveReference(), + info = item.timestamp?.resolveReference(), + subtitleEndOffset = item.subtitleEndOffset, + subtitleIconRes = item.subtitleIconRes, + onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, + modifier = Modifier + .then( + if (isLast && index == list.lastIndex) { + Modifier + .padding(bottom = TangemTheme.dimens.spacing12) + .clip( + shape = RoundedCornerShape( + bottomStart = TangemTheme.dimens.radius16, + bottomEnd = TangemTheme.dimens.radius16, + ), + ) + } else { + Modifier }, ) - } - } - } - } -} - -@Composable -private fun RecipientWalletListItem( - item: SendRecipientListContent.Wallets, - clickIntents: SendClickIntents, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .background(TangemTheme.colors.background.action) - .padding(top = TangemTheme.dimens.spacing12), - ) { - if (item.list.isNotEmpty()) { - Text( - text = stringResource(R.string.send_recipient_wallets_title), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - modifier = Modifier - .fillMaxWidth() - .padding( - start = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing12, - bottom = TangemTheme.dimens.spacing8, - ), - ) - } - item.list.forEachIndexed { _, wallet -> - val title = wallet.title.resolveReference() - ListItemWithIcon( - title = wallet.title.resolveReference(), - subtitle = wallet.subtitle.resolveReference(), - onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, - ) - } - if (!item.isWalletsOnly) { - val topPadding = if (item.list.isNotEmpty()) { - TangemTheme.dimens.spacing8 - } else { - TangemTheme.dimens.spacing0 - } - Text( - text = stringResource(R.string.send_recent_transactions), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - modifier = Modifier - .fillMaxWidth() - .padding( - top = topPadding, - bottom = TangemTheme.dimens.spacing8, - start = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing12, - ), + .background(TangemTheme.colors.background.action), ) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 6c66189f98..f2eedb220d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -57,7 +57,7 @@ internal interface SendClickIntents { fun showFee() - fun onExploreClick(txUrl: String) + fun onExploreClick() fun onShareClick() 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 c9969641c5..7b47227a2e 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 @@ -4,8 +4,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* -import androidx.paging.PagingData -import androidx.paging.cachedIn import arrow.core.Either import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData @@ -28,8 +26,7 @@ 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.GetTxHistoryItemsCountUseCase -import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase +import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId @@ -54,6 +51,7 @@ 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 @@ -72,8 +70,7 @@ internal class SendViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getWalletsUseCase: GetWalletsUseCase, private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, - private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, - private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, + private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val createTransactionUseCase: CreateTransactionUseCase, @@ -106,7 +103,8 @@ internal class SendViewModel @Inject constructor( private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() private var innerRouter: InnerSendRouter by Delegates.notNull() - private var stateRouter: StateRouter by Delegates.notNull() + var stateRouter: StateRouter by Delegates.notNull() + private set private val stateFactory = SendStateFactory( clickIntents = this, @@ -142,6 +140,7 @@ internal class SendViewModel @Inject constructor( coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, + stateRouterProvider = Provider { stateRouter }, clickIntents = this, getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase, ) @@ -151,6 +150,7 @@ internal class SendViewModel @Inject constructor( coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, + stateRouterProvider = Provider { stateRouter }, currencyChecksRepository = currencyChecksRepository, clickIntents = this, ) @@ -190,7 +190,6 @@ internal class SendViewModel @Inject constructor( fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) { innerRouter = router this.stateRouter = stateRouter - uiState = uiState.copy(currentState = stateRouter.currentState) } private fun subscribeOnCurrencyStatusUpdates(owner: LifecycleOwner) { @@ -301,12 +300,10 @@ internal class SendViewModel @Inject constructor( combine( flow = getUserWallets().conflate(), flow2 = getTxHistory().conflate(), - flow3 = getTxHistoryCount().conflate(), - ) { wallets, txHistory, txHistoryCount -> - stateFactory.onLoadedRecipientList( + ) { wallets, txHistory -> + uiState = stateFactory.onLoadedRecipientList( wallets = wallets, txHistory = txHistory, - txHistoryCount = txHistoryCount, ) } .flowOn(dispatchers.io) @@ -348,34 +345,18 @@ internal class SendViewModel @Inject constructor( } } - private fun getTxHistory(): Flow> { - return txHistoryItemsUseCase( + private fun getTxHistory(): Flow> { + return getFixedTxHistoryItemsUseCase( userWalletId = userWalletId, currency = cryptoCurrency, ).fold( - ifRight = { - it.distinctUntilChanged().cachedIn(viewModelScope) - }, - ifLeft = { - emptyFlow() - }, + ifRight = { it.distinctUntilChanged() }, + ifLeft = { emptyFlow() }, ) } - private fun getTxHistoryCount(): Flow { - return flow { - txHistoryItemsCountUseCase( - userWalletId = userWalletId, - currency = cryptoCurrency, - ).fold( - ifRight = { emit(it) }, - ifLeft = { emit(0) }, - ) - } - } - private fun onStateActive() { - uiState.currentState + stateRouter.currentState .onEach { when (it.type) { SendUiStateType.Fee -> if (!it.isFromConfirmation) loadFee() @@ -499,11 +480,13 @@ internal class SendViewModel @Inject constructor( } private suspend fun validateAddress(value: String): Boolean { - return validateWalletAddressUseCase( + val isValidAddress = validateWalletAddressUseCase( userWalletId = userWalletId, network = cryptoCurrency.network, address = value, ).getOrElse { false } + onEnteredValidAddress(isValidAddress) + return isValidAddress } private suspend fun checkIfXrpAddressValue(value: String): Boolean { @@ -515,6 +498,16 @@ internal class SendViewModel @Inject constructor( true } ?: false } + + private fun onEnteredValidAddress(isValidAddress: Boolean) { + val recipientState = uiState.recipientState ?: return + uiState = uiState.copy( + recipientState = recipientState.copy( + recent = recipientState.recent.map { it.copy(isVisible = !isValidAddress) }.toPersistentList(), + wallets = recipientState.wallets.map { it.copy(isVisible = !isValidAddress) }.toPersistentList(), + ), + ) + } // endregion // region fee @@ -613,9 +606,9 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ScreenReopened(SendScreenSource.Fee)) } - override fun onExploreClick(txUrl: String) { + override fun onExploreClick() { analyticsEventHandler.send(SendAnalyticEvents.ExploreButtonClicked) - innerRouter.openUrl(txUrl) + innerRouter.openUrl(uiState.sendState.txUrl) } override fun onShareClick() {