From 982766e5c663615a3458fcbec753519ddb634dfe Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 16:53:21 +0500 Subject: [PATCH 1/4] Updated on 2026-08-14 --- .../impl/presentation/viewmodel/SendViewModel.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 ff49afe1c3..db5be852ce 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 @@ -376,8 +376,11 @@ internal class SendViewModel @Inject constructor( feeCryptoCurrencyStatus = feeCurrencyStatus subscribeOnQRScannerResult() when { - uiState.sendState?.isSuccess == true -> { - stateRouter.showSend() + uiState.sendState?.isSuccess != true -> { + uiState = stateFactory.getReadyState() + getWalletsAndRecent() + stateRouter.showRecipient() + updateNotifications() } transactionId != null && amount != null && destinationAddress != null -> { loadFee() @@ -385,12 +388,6 @@ internal class SendViewModel @Inject constructor( stateRouter.showSend() updateNotifications() } - else -> { - uiState = stateFactory.getReadyState() - getWalletsAndRecent() - stateRouter.showRecipient() - updateNotifications() - } } } From 01a49e405eadc4762814f31f2d0a3ebcf7271143 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 16:55:42 +0500 Subject: [PATCH 2/4] Updated on 2026-08-14 --- .../impl/presentation/ui/SendNavigationButtons.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 93cc9067fd..3f5d13ae28 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 @@ -4,7 +4,10 @@ import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -35,6 +38,7 @@ import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.utils.getFiatFormatted +import com.tangem.features.send.impl.presentation.utils.getFiatString @Composable internal fun SendNavigationButtons( @@ -180,12 +184,12 @@ private fun SendingText( currencySymbol = feeState.appCurrency.symbol, currencyCode = feeState.appCurrency.code, ) - val feeValue = getFiatFormatted( + val feeValue = getFiatString( value = feeState.fee?.amount?.value, - currencySymbol = feeState.appCurrency.symbol, - currencyCode = feeState.appCurrency.code, + rate = feeState.rate, + appCurrency = feeState.appCurrency, ) - val textResource = remember(sendingValue, feeValue) { + val textResource = remember(uiState) { resourceReference( id = R.string.send_summary_transaction_description, formatArgs = wrappedList(sendingValue, feeValue), From e63ca27981818d44f1266a17bcd219c226e635eb Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 16:57:16 +0500 Subject: [PATCH 3/4] Updated on 2026-08-14 --- .../presentation/viewmodel/SendViewModel.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 db5be852ce..921c3124e0 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 @@ -58,7 +58,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.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -73,7 +72,6 @@ internal class SendViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, - private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getWalletsUseCase: GetWalletsUseCase, @@ -96,6 +94,8 @@ internal class SendViewModel @Inject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, + private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, + private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -866,8 +866,8 @@ internal class SendViewModel @Inject constructor( ifRight = { uiState = stateFactory.getSendingStateUpdate(isSending = false) updateTransactionStatus(txData) - scheduleBalanceUpdate() addTokenToWalletIfNeeded() + scheduleUpdates() sendScreenAnalyticSender.sendTransaction() }, ) @@ -894,12 +894,17 @@ internal class SendViewModel @Inject constructor( uiState = stateFactory.getTransactionSendState(txData, txUrl) } - private fun scheduleBalanceUpdate() { - viewModelScope.launch(dispatchers.io) { - delay(BALANCE_UPDATE_DELAY) - fetchCurrencyStatusUseCase.invoke( - userWalletId = userWalletId, - id = cryptoCurrency.id, + private fun scheduleUpdates() { + viewModelScope.launch(dispatchers.main) { + // we should update network to find pending tx after 1 sec + fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrency.network)) + } + viewModelScope.launch(dispatchers.main) { + // we should update network for new balance + updateDelayedCurrencyStatusUseCase( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + delayMillis = BALANCE_UPDATE_DELAY, refresh = true, ) } From 1b4df199307d424a51e7643c72562890b8b978e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 May 2024 18:58:30 +0500 Subject: [PATCH 4/4] Updated on 2026-08-14 --- .../repository/DefaultNetworksRepository.kt | 25 +++++++++++-- .../tokens/GetNetworkAddressesUseCase.kt | 19 ++-------- .../tokens/repository/NetworksRepository.kt | 2 + .../SendRecipientWalletListConverter.kt | 31 +++++++++------- .../presentation/viewmodel/SendViewModel.kt | 37 ++++++++----------- 5 files changed, 61 insertions(+), 53 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index f25a65291c..57f55c1aab 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -1,6 +1,7 @@ package com.tangem.data.tokens.repository import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.address.AddressType import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory import com.tangem.data.tokens.utils.NetworkStatusFactory @@ -77,6 +78,21 @@ internal class DefaultNetworksRepository( return blockchain == Blockchain.Aptos } + override suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List { + // Get list of currencies matching [network] + val currencies = getCurrencies(userWalletId) + .filter { currency -> network.id == currency.network.id } + + // There is no currencies matching given [networks] in [userWalletId] + if (currencies.toList().isEmpty()) return emptyList() + + return currencies.toList().map { currency -> + walletManagersFacade.getAddresses(userWalletId, currency.network) + .firstOrNull { it.type == AddressType.Default } + ?.value.orEmpty() + } + } + private suspend fun fetchNetworksStatusesIfCacheExpired( userWalletId: UserWalletId, networks: Set, @@ -174,11 +190,16 @@ internal class DefaultNetworksRepository( } private suspend fun getCurrencies(userWalletId: UserWalletId, networks: Set): Sequence { + val currencies = getCurrencies(userWalletId) + return currencies.filter { networks.contains(it.network) } + } + + private suspend fun getCurrencies(userWalletId: UserWalletId): Sequence { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "Unable to find user wallet with provided ID: $userWalletId" } - val currencies = if (userWallet.isMultiCurrency) { + return if (userWallet.isMultiCurrency) { val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) { "Unable to find tokens response for user wallet with provided ID: $userWalletId" } @@ -194,8 +215,6 @@ internal class DefaultNetworksRepository( sequenceOf(currency) } } - - return currencies.filter { networks.contains(it.network) } } private suspend fun invalidateCacheKeyIfNeeded( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt index aea1cad8eb..0fa37a5a07 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -1,27 +1,14 @@ package com.tangem.domain.tokens import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.wallets.models.UserWalletId -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map class GetNetworkAddressesUseCase( internal val networksRepository: NetworksRepository, ) { - operator fun invoke(userWalletId: UserWalletId, network: Network): Flow> = - networksRepository.getNetworkStatusesUpdates(userWalletId, setOf(network)) - .map { networkStatuses -> - networkStatuses.filter { it.network.id == network.id } - .map { networkStatus -> - when (val status = networkStatus.value) { - is NetworkStatus.NoAccount -> status.address.defaultAddress.value - is NetworkStatus.Unreachable -> status.address?.defaultAddress?.value.orEmpty() - is NetworkStatus.Verified -> status.address.defaultAddress.value - else -> "" - } - } - } + suspend fun invokeSync(userWalletId: UserWalletId, network: Network): List { + return networksRepository.getNetworkAddresses(userWalletId, network) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index 6ec3fbb11a..ebece1356e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -45,4 +45,6 @@ interface NetworksRepository { ): Set fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean + + suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List } \ No newline at end of file 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 index dae1452696..35d56f9846 100644 --- 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 @@ -9,7 +9,6 @@ import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyLis import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.flow.filter internal class SendRecipientWalletListConverter : Converter, PersistentList> { @@ -24,19 +23,25 @@ internal class SendRecipientWalletListConverter : return this.filterNotNull() .filter { it.address.isNotBlank() } .groupBy { item -> item.name } - .values.map { - it.mapIndexed { index, item -> - val name = if (it.size > 1) { - "${item.name} ${index.inc()}" - } else { - item.name + .values.map { wallets -> + val groupedByWallet = wallets.groupBy { it.userWalletId } + var i = 0 + groupedByWallet + .flatMap { item -> + item.value.map { wallet -> + val name = if (groupedByWallet.size > 1) { + "${wallet.name} ${++i}" + } else { + wallet.name + } + + SendRecipientListContent( + id = "${WALLET_KEY_TAG}${walletsCounter++}", + title = TextReference.Str(wallet.address), + subtitle = TextReference.Str(name), + ) + } } - SendRecipientListContent( - id = "${WALLET_KEY_TAG}${walletsCounter++}", - title = TextReference.Str(item.address), - subtitle = TextReference.Str(name), - ) - } } .flatten() .toPersistentList() 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 921c3124e0..65d93773c7 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,6 +4,7 @@ import android.os.SystemClock import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.util.fastDistinctBy import androidx.lifecycle.* import arrow.core.Either import arrow.core.getOrElse @@ -271,6 +272,7 @@ internal class SendViewModel @Inject constructor( .saveIn(balanceHidingJobHolder) } + // TODO [REDACTED_JIRA] private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) { if (cryptoCurrency is CryptoCurrency.Coin) { getCurrencyStatusUpdates( @@ -405,43 +407,36 @@ internal class SendViewModel @Inject constructor( ?.toAvailableWallets() .orEmpty() }.onSuccess { result -> - combine(*result.toTypedArray()) { it } - .onEach { wallets -> - userWallets = wallets.flatMap { it }.toList() - uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) - } - .flowOn(dispatchers.main) - .launchIn(viewModelScope) + userWallets = result + uiState = stateFactory.onLoadedWalletsList(wallets = userWallets) }.onFailure { uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) } } } - private suspend fun List.toAvailableWallets(): List>> = + private suspend fun List.toAvailableWallets(): List = filterNot { it.walletId == userWalletId || it.isLocked } .mapNotNull { wallet -> - val status = if (!wallet.isMultiCurrency) { + val addresses = if (!wallet.isMultiCurrency) { getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { if (it.network.id == cryptoCurrency.network.id) { - getNetworkAddressesUseCase(wallet.walletId, it.network) + getNetworkAddressesUseCase.invokeSync(wallet.walletId, it.network) } else { null } } } else { - getNetworkAddressesUseCase(wallet.walletId, cryptoCurrency.network) + getNetworkAddressesUseCase.invokeSync(wallet.walletId, cryptoCurrency.network) } - status?.map { addresses -> - addresses.map { address -> - AvailableWallet( - name = wallet.name, - address = address, - userWalletId = wallet.walletId, - ) - } - } - } + addresses?.map { address -> + AvailableWallet( + name = wallet.name, + address = address, + userWalletId = wallet.walletId, + ) + }?.fastDistinctBy { it.address } + }.flatten() private suspend fun getTxHistory() { val txHistoryList = getFixedTxHistoryItemsUseCase.getSync(