From 042df9828f63eb8155f4caff700a926aeb8801e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Sep 2025 18:00:10 +0300 Subject: [PATCH 01/20] Updated on 2026-08-14 --- .../datasource/api/express/TangemExpressApi.kt | 1 + .../com/tangem/data/swap/DefaultSwapRepositoryV2.kt | 3 +++ .../domain/express/models/ExpressOperationType.kt | 6 ++++++ .../java/com/tangem/domain/swap/SwapRepositoryV2.kt | 3 +++ .../tangem/domain/swap/usecase/GetSwapDataUseCase.kt | 3 +++ .../confirm/model/SendWithSwapConfirmModel.kt | 2 ++ .../confirm/model/SwapTransactionSender.kt | 5 +++++ features/swap/data/build.gradle.kts | 1 + .../com/tangem/feature/swap/DefaultSwapRepository.kt | 3 +++ features/swap/domain/api/build.gradle.kts | 1 + .../tangem/feature/swap/domain/api/SwapRepository.kt | 2 ++ features/swap/domain/build.gradle.kts | 1 + .../com/tangem/feature/swap/domain/SwapInteractor.kt | 2 ++ .../tangem/feature/swap/domain/SwapInteractorImpl.kt | 12 +++++++++++- features/swap/impl/build.gradle.kts | 1 + .../java/com/tangem/feature/swap/model/SwapModel.kt | 2 ++ 16 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressOperationType.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt index a57712606b..3a4c3d9e84 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/TangemExpressApi.kt @@ -66,6 +66,7 @@ interface TangemExpressApi { @Query("requestId") requestId: String, @Query("refundAddress") refundAddress: String?, // for cex only @Query("refundExtraId") refundExtraId: String?, // for cex only + @Query("partnerOperationType") partnerOperationType: String?, // swap/ swap-and-send ): ApiResponse @GET("exchange-status") diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt index 82aa29d68a..5f67f6400f 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt @@ -18,6 +18,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.express.ExpressRepository import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.express.models.ExpressRateType @@ -208,6 +209,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( toAddress: String, expressProvider: ExpressProvider, rateType: ExpressRateType, + expressOperationType: ExpressOperationType, ): SwapDataModel = withContext(coroutineDispatcher.io) { val requestId = UUID.randomUUID().toString() val fromCryptoCurrency = fromCryptoCurrencyStatus.currency @@ -239,6 +241,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( refundAddress = refundData?.refundAddress, refundExtraId = refundData?.refundExtraId, userWalletId = userWallet.walletId.stringValue, + partnerOperationType = expressOperationType.value, refCode = ExpressUtils.getRefCode( userWallet = userWallet, appPreferencesStore = appPreferencesStore, diff --git a/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressOperationType.kt b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressOperationType.kt new file mode 100644 index 0000000000..7c836ad588 --- /dev/null +++ b/domain/express/models/src/main/java/com/tangem/domain/express/models/ExpressOperationType.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.express.models + +enum class ExpressOperationType(val value: String) { + SWAP("swap"), + SEND_WITH_SWAP("swap-and-send"), +} \ No newline at end of file diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt b/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt index b94e923f2f..d25708c229 100644 --- a/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt +++ b/domain/swap/src/main/java/com/tangem/domain/swap/SwapRepositoryV2.kt @@ -1,5 +1,6 @@ package com.tangem.domain.swap +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.express.models.ExpressRateType @@ -74,6 +75,7 @@ interface SwapRepositoryV2 { * @param toAddress destination address * @param expressProvider selected swap provider * @param rateType selected provider rate type + * @param expressOperationType operation type swap or send with swap */ suspend fun getSwapData( userWallet: UserWallet, @@ -83,6 +85,7 @@ interface SwapRepositoryV2 { toAddress: String, expressProvider: ExpressProvider, rateType: ExpressRateType, + expressOperationType: ExpressOperationType, ): SwapDataModel /** diff --git a/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt index ac60b660e2..0f0638c9a8 100644 --- a/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt +++ b/domain/swap/src/main/java/com/tangem/domain/swap/usecase/GetSwapDataUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.swap.usecase import arrow.core.Either import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressRateType import com.tangem.domain.models.currency.CryptoCurrency @@ -25,6 +26,7 @@ class GetSwapDataUseCase( toAddress: String, expressProvider: ExpressProvider, rateType: ExpressRateType, + expressOperationType: ExpressOperationType, ): Either = Either.catch { swapRepositoryV2.getSwapData( userWallet = userWallet, @@ -34,6 +36,7 @@ class GetSwapDataUseCase( toAddress = toAddress, expressProvider = expressProvider, rateType = rateType, + expressOperationType = expressOperationType, ) }.mapLeft { throwable -> swapErrorResolver.resolve(throwable) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index cebaaba9dd..a434134267 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -17,6 +17,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -293,6 +294,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( } router.replaceAll(SendWithSwapRoute.Success) }, + expressOperationType = ExpressOperationType.SEND_WITH_SWAP, ) } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt index 458d1ebd4e..91239d4afe 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt @@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model import arrow.core.getOrElse import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -40,6 +41,7 @@ internal class SwapTransactionSender @AssistedInject constructor( onSendSuccess: (String, Long, SwapDataModel) -> Unit, onExpressError: (ExpressError) -> Unit, onSendError: (SendTransactionError?) -> Unit, + expressOperationType: ExpressOperationType, ) { val provider = confirmData.quote?.provider ?: return @@ -50,6 +52,7 @@ internal class SwapTransactionSender @AssistedInject constructor( onExpressError = onExpressError, onSendSuccess = onSendSuccess, onSendError = onSendError, + expressOperationType = expressOperationType, ) ExpressProviderType.DEX, ExpressProviderType.DEX_BRIDGE, @@ -67,6 +70,7 @@ internal class SwapTransactionSender @AssistedInject constructor( onSendSuccess: (String, Long, SwapDataModel) -> Unit, onExpressError: (ExpressError) -> Unit, onSendError: (SendTransactionError?) -> Unit, + expressOperationType: ExpressOperationType, ) { val fromStatus = confirmData.fromCryptoCurrencyStatus ?: return val toStatus = confirmData.toCryptoCurrencyStatus ?: return @@ -92,6 +96,7 @@ internal class SwapTransactionSender @AssistedInject constructor( toAddress = destination, expressProvider = provider, rateType = rateType, + expressOperationType, ).getOrElse { onExpressError(it); return } createAndSendCexTransaction( diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 5b0d422759..5a76552289 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.transaction.models) + implementation(projects.domain.express.models) /** Data */ implementation(projects.data.common) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 96a200e419..a05a0f0b26 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -27,6 +27,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -283,6 +284,7 @@ internal class DefaultSwapRepository( providerId: String, rateType: RateType, toAddress: String, + expressOperationType: ExpressOperationType, refundAddress: String?, // for cex only refundExtraId: String?, // for cex only ): Either { @@ -304,6 +306,7 @@ internal class DefaultSwapRepository( requestId = requestId, refundAddress = refundAddress, refundExtraId = refundExtraId, + partnerOperationType = expressOperationType.value, userWalletId = userWallet.walletId.stringValue, refCode = ExpressUtils.getRefCode( userWallet = userWallet, diff --git a/features/swap/domain/api/build.gradle.kts b/features/swap/domain/api/build.gradle.kts index 38ecfe752e..cee9c5f63d 100644 --- a/features/swap/domain/api/build.gradle.kts +++ b/features/swap/domain/api/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { implementation(projects.features.swap.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.express.models) implementation(deps.arrow.core) diff --git a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index 552ee983aa..7473ad49ae 100644 --- a/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/api/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.domain.api import arrow.core.Either +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -64,6 +65,7 @@ interface SwapRepository { providerId: String, rateType: RateType, toAddress: String, + expressOperationType: ExpressOperationType, refundAddress: String? = null, // for cex only refundExtraId: String? = null, // for cex only ): Either diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 256c69cbac..8adde9bd53 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.domain.txhistory.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) + implementation(projects.domain.express.models) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 3646a0d6b5..97a8cfc718 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId @@ -65,6 +66,7 @@ interface SwapInteractor { amountToSwap: String, includeFeeInAmount: IncludeFeeInAmount, fee: TxFee, + expressOperationType: ExpressOperationType, ): SwapTransactionState // suspend fun updateQuotesStateWithSelectedFee( diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index de44a22010..1d9a24f713 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network @@ -288,6 +289,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( selectedFee = selectedFee, amount = amount, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, + expressOperationType = ExpressOperationType.SWAP, ) } ExchangeProviderType.CEX -> { @@ -314,6 +316,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( selectedFee: FeeType, amount: SwapAmount, isBalanceWithoutFeeEnough: Boolean, + expressOperationType: ExpressOperationType, ): Pair { val maybeQuotes = repository.findBestQuote( userWallet = userWallet, @@ -350,6 +353,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( toToken = toToken, amount = amount, selectedFee = selectedFee, + expressOperationType = expressOperationType, ) } else { provider to getQuotesState( @@ -496,6 +500,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( amountToSwap: String, includeFeeInAmount: IncludeFeeInAmount, fee: TxFee, + expressOperationType: ExpressOperationType, ): SwapTransactionState { Timber.i( """ @@ -530,6 +535,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( amount = amountToSwapWithFee, txFee = fee, swapProvider = swapProvider, + expressOperationType = expressOperationType, ) } ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { @@ -677,6 +683,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( amount: SwapAmount, txFee: TxFee, swapProvider: SwapProvider, + expressOperationType: ExpressOperationType, ): SwapTransactionState { val exchangeData = repository.getExchangeData( userWallet = userWallet, @@ -690,9 +697,10 @@ internal class SwapInteractorImpl @AssistedInject constructor( toDecimals = currencyToGet.currency.decimals, providerId = swapProvider.providerId, rateType = RateType.FLOAT, + expressOperationType = expressOperationType, toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(), refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value, - refundExtraId = null, // currently always null + refundExtraId = null, // currently always null, ).getOrElse { return SwapTransactionState.Error.ExpressError(it) } val exchangeDataCex = @@ -1162,6 +1170,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( toToken: CryptoCurrencyStatus, amount: SwapAmount, selectedFee: FeeType, + expressOperationType: ExpressOperationType, ): SwapState { return repository.getExchangeData( userWallet = userWallet, @@ -1177,6 +1186,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( rateType = RateType.FLOAT, toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(), refundAddress = fromToken.value.networkAddress?.defaultAddress?.value, + expressOperationType = expressOperationType, ).fold( ifRight = { swapData -> val transaction = swapData.transaction as ExpressTransactionModel.DEX diff --git a/features/swap/impl/build.gradle.kts b/features/swap/impl/build.gradle.kts index ed1a7f3f50..c87d68c2df 100644 --- a/features/swap/impl/build.gradle.kts +++ b/features/swap/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.domain.promo.models) implementation(projects.domain.txhistory) implementation(projects.domain.txhistory.models) + implementation(projects.domain.express.models) /** Feature modules */ implementation(projects.features.swap.domain) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 2e8026f8db..b1c067828a 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.utils.InputNumberFormatter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.express.models.ExpressOperationType import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase @@ -613,6 +614,7 @@ internal class SwapModel @Inject constructor( amountToSwap = requireNotNull(dataState.amount), includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount, fee = fee, + expressOperationType = ExpressOperationType.SWAP, ) }.onSuccess { when (it) { From 7efaa644602b67d70d01d5947116f08104983b7c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 14:36:35 +0500 Subject: [PATCH 02/20] Updated on 2026-08-14 --- .../destination/entity/DestinationUM.kt | 1 + .../SendDestinationInitialStateTransformer.kt | 1 + .../SendDestinationRecentListTransformer.kt | 2 +- .../SendDestinationValidationResultTransformer.kt | 1 + .../destination/ui/DestinationBlock.kt | 1 + .../destination/ui/SendDestinationContent.kt | 15 +++++++++------ .../v2/impl/amount/SwapAmountComponentParams.kt | 3 ++- .../swap/v2/impl/amount/model/SwapAmountModel.kt | 3 ++- .../sendviaswap/DefaultSendWithSwapComponent.kt | 2 +- .../success/ui/SendWithSwapSuccessContent.kt | 1 + 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt index f6ebfe61be..ed79e08793 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt @@ -18,6 +18,7 @@ sealed class DestinationUM { val isValidating: Boolean = false, val isInitialized: Boolean = false, val isRedesignEnabled: Boolean = false, + val isRecentHidden: Boolean, ) : DestinationUM() data class Empty( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt index 39cb1fd77b..e1ee0f98fe 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt @@ -60,6 +60,7 @@ internal class SendDestinationInitialStateTransformer( networkName = cryptoCurrency.network.name, isValidating = false, isRedesignEnabled = isRedesignEnabled, + isRecentHidden = false, ) } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt index 90dbda803c..aaa18babfe 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt @@ -2,9 +2,9 @@ package com.tangem.features.send.v2.subcomponents.destination.model.transformers import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.TxInfo +import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientHistoryListConverter import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientWalletListConverter -import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationWalletUM import com.tangem.utils.transformer.Transformer diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationValidationResultTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationValidationResultTransformer.kt index 3ae7039b38..da3d010f3f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationValidationResultTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationValidationResultTransformer.kt @@ -54,6 +54,7 @@ internal class SendDestinationValidationResultTransformer( wallets = state.wallets.map { wallet -> wallet.copy(isVisible = !isValidAddress && (wallet.isLoading || wallet.title != TextReference.EMPTY)) }.toPersistentList(), + isRecentHidden = isValidAddress, ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt index f8cf59bdb0..f4e4cb31b7 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt @@ -211,6 +211,7 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt index da247fa248..6f655f90bf 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt @@ -25,12 +25,12 @@ import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.send.v2.impl.R -import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource -import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationClickIntents import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.send.v2.impl.R +import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource +import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationClickIntents import kotlinx.collections.immutable.ImmutableList private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY" @@ -74,13 +74,14 @@ internal fun SendDestinationContent( ) listHeaderItem( titleRes = R.string.send_recipient_wallets_title, - isVisible = wallets.isNotEmpty() && wallets.first().isVisible, + isVisible = wallets.isNotEmpty() && wallets.first().isVisible && !state.isRecentHidden, isFirst = true, ) listItem( list = wallets, isLast = recipients.any { !it.isVisible }, isBalanceHidden = isBalanceHidden, + isRecentHidden = state.isRecentHidden, onClick = { title -> clickIntents.onRecipientAddressValueChange( title, @@ -90,13 +91,14 @@ internal fun SendDestinationContent( ) listHeaderItem( titleRes = R.string.send_recent_transactions, - isVisible = recipients.isNotEmpty() && recipients.first().isVisible, + isVisible = recipients.isNotEmpty() && recipients.first().isVisible && !state.isRecentHidden, isFirst = wallets.any { !it.isVisible }, ) listItem( list = recipients, isLast = true, isBalanceHidden = isBalanceHidden, + isRecentHidden = state.isRecentHidden, onClick = { title -> clickIntents.onRecipientAddressValueChange( title, @@ -257,6 +259,7 @@ private fun LazyListScope.listItem( list: ImmutableList, isLast: Boolean, isBalanceHidden: Boolean, + isRecentHidden: Boolean, onClick: (String) -> Unit, ) { items( @@ -267,7 +270,7 @@ private fun LazyListScope.listItem( val item = list[index] val title = item.title.resolveReference() - if (item.isVisible) { + if (item.isVisible && !isRecentHidden) { ListItemWithIcon( title = title, subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponentParams.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponentParams.kt index 109c84dd81..2eca8f1a8e 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponentParams.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponentParams.kt @@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.swap.models.SwapDirection import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -32,7 +33,7 @@ internal sealed class SwapAmountComponentParams { override val filterProviderTypes: List = emptyList(), val title: TextReference, val callback: SwapAmountComponent.ModelCallback, - val currentRoute: Flow, + val currentRoute: Flow, ) : SwapAmountComponentParams() data class AmountBlockParams( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index ca3b3093f2..a6f5cf8206 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -53,6 +53,7 @@ import com.tangem.features.swap.v2.impl.amount.model.transformers.* import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent import com.tangem.features.swap.v2.impl.common.SwapAlertFactory import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer import com.tangem.utils.coroutines.PeriodicTask @@ -689,7 +690,7 @@ internal class SwapAmountModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, route) -> + ).filter { (_, route) -> route is SendWithSwapRoute.Amount }.onEach { (state, route) -> params.callback.onNavigationResult( NavigationUM.Content( title = resourceReference(R.string.common_amount), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt index 712782f490..b59ca862d9 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt @@ -136,7 +136,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor( params = SwapAmountComponentParams.AmountParams( amountUM = model.uiState.value.amountUM, title = resourceReference(R.string.common_send), - currentRoute = model.currentRoute.filterIsInstance(), + currentRoute = model.currentRoute, isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = model.analyticCategoryName, primaryCryptoCurrencyStatusFlow = model.primaryCryptoCurrencyStatusFlow, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt index 6e5406b9e6..252e841b90 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt @@ -301,6 +301,7 @@ private fun SendWithSwapSuccessContent_Preview() { isValidating = false, isInitialized = false, isRedesignEnabled = false, + isRecentHidden = false, ), feeSelectorUM = FeeSelectorUM.Content( fees = TransactionFee.Single( From 78698a0fe761aa304fbbc07403b49c89d7663986 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 16:51:57 +0500 Subject: [PATCH 03/20] Updated on 2026-08-14 --- .../amount/model/transformers/SwapAmountPasteTransformer.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPasteTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPasteTransformer.kt index 2b6eb14c59..3462db7349 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPasteTransformer.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountPasteTransformer.kt @@ -7,7 +7,7 @@ import com.tangem.utils.transformer.Transformer internal object SwapAmountPasteTransformer : Transformer { override fun transform(prevState: SwapAmountUM): SwapAmountUM { - return prevState.updateAmount( + val updatedState = prevState.updateAmount( onPrimaryAmount = { copy( amountField = AmountPastedTriggerDismissTransformer.transform( @@ -23,5 +23,9 @@ internal object SwapAmountPasteTransformer : Transformer { ) }, ) + + return (updatedState as? SwapAmountUM.Content)?.copy( + isPrimaryButtonEnabled = prevState.isPrimaryButtonEnabled, + ) ?: updatedState } } \ No newline at end of file From b623a457d917e5830f98da876c12ad9b0cbe8a46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 16:52:24 +0500 Subject: [PATCH 04/20] Updated on 2026-08-14 --- .../data/swap/DefaultSwapRepositoryV2.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt index 5f67f6400f..277a7125be 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt @@ -17,11 +17,7 @@ import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.exchangeservice.swap.ExpressUtils import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.express.ExpressRepository -import com.tangem.domain.express.models.ExpressError -import com.tangem.domain.express.models.ExpressOperationType -import com.tangem.domain.express.models.ExpressProvider -import com.tangem.domain.express.models.ExpressProviderType -import com.tangem.domain.express.models.ExpressRateType +import com.tangem.domain.express.models.* import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.NetworkStatus @@ -97,13 +93,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( it.currency.network.backendId == pair.to.network } - if (statusFrom != null && statusTo != null) { + val mappedProviders = pair.providers.mapNotNull { + mappedProviders[it.providerId] + } + + if (statusFrom != null && statusTo != null && mappedProviders.isNotEmpty()) { SwapPairModel( from = statusFrom, to = statusTo, - providers = pair.providers.mapNotNull { - mappedProviders[it.providerId] - }, + providers = mappedProviders, ) } else { null @@ -152,13 +150,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( val currencyStatusFrom = createSendWithSwapCryptoCurrencyStatus(statusFromDeferred.await()) val currencyStatusTo = createSendWithSwapCryptoCurrencyStatus(statusToDeferred.await()) - if (currencyStatusFrom != null && currencyStatusTo != null) { + val mappedProvider = pair.providers.mapNotNull { + mappedProviders[it.providerId] + } + + if (currencyStatusFrom != null && currencyStatusTo != null && mappedProvider.isNotEmpty()) { SwapPairModel( from = currencyStatusFrom, to = currencyStatusTo, - providers = pair.providers.mapNotNull { - mappedProviders[it.providerId] - }, + providers = mappedProvider, ) } else { null From 3af4539567bb71044fc99ce712b351b68f1ac566 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 16:52:50 +0500 Subject: [PATCH 05/20] Updated on 2026-08-14 --- .../features/send/v2/api/SendComponent.kt | 2 +- .../entrypoint/model/SendEntryPointModel.kt | 17 +++++++-------- .../features/send/v2/send/model/SendModel.kt | 6 +++--- .../amount/SendAmountComponent.kt | 2 +- .../amount/SendAmountReduceTrigger.kt | 10 ++++----- .../amount/model/SendAmountModel.kt | 13 ++++++++++-- .../swap/v2/api/SendWithSwapComponent.kt | 2 +- .../subcomponents/SwapAmountUpdateTrigger.kt | 2 +- .../amount/DefaultSwapAmountUpdateTrigger.kt | 10 ++++----- .../v2/impl/amount/SwapAmountComponent.kt | 2 +- .../v2/impl/amount/model/SwapAmountModel.kt | 21 +++++++++++++++---- .../sendviaswap/model/SendWithSwapModel.kt | 4 ++-- 12 files changed, 56 insertions(+), 35 deletions(-) diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendComponent.kt index 34f945d37c..88b5f8d9e2 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendComponent.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendComponent.kt @@ -20,6 +20,6 @@ interface SendComponent : ComposableContentComponent { interface Factory : ComponentFactory interface ModelCallback { - fun onConvertToAnotherToken(lastAmount: String) + fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt index f18192f9f9..24f122f9bd 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt @@ -30,9 +30,11 @@ internal class SendEntryPointModel @Inject constructor( ChooseManagedTokensComponent.ModelCallback { private var lastSavedAmount = "" + private var isEnterInFiat = false - override fun onConvertToAnotherToken(lastAmount: String) { + override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) { lastSavedAmount = lastAmount + isEnterInFiat = isEnterInFiatSelected modelScope.launch { val showSendViaSwapNotification = shouldShowNotificationUseCase( NotificationId.SendViaSwapTokenSelectorNotification.key, @@ -45,12 +47,11 @@ internal class SendEntryPointModel @Inject constructor( } } - override fun onCloseSwap(lastAmount: String) { + override fun onCloseSwap(lastAmount: String, isEnterInFiatSelected: Boolean) { lastSavedAmount = lastAmount + isEnterInFiat = isEnterInFiatSelected modelScope.launch { - if (lastAmount.isNotBlank()) { - sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount) - } + sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount, isEnterInFiat) router.replaceAll(SendEntryRoute.Send) } } @@ -58,9 +59,7 @@ internal class SendEntryPointModel @Inject constructor( @Suppress("MagicNumber") override fun onResult() { modelScope.launch { - if (lastSavedAmount.isNotBlank()) { - swapAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount) - } + swapAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount, isEnterInFiat) // Workaround in order to execute correct exit animation on SendEntryRoute.ChooseToken router.pop() delay(10L) @@ -70,7 +69,7 @@ internal class SendEntryPointModel @Inject constructor( override fun onBack() { modelScope.launch { - sendAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount) + sendAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount, isEnterInFiat) router.pop() } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index f0a63844c1..1aa522334b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -227,14 +227,14 @@ internal class SendModel @Inject constructor( } } - override fun onConvertToAnotherToken(lastAmount: String) { + override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) { analyticsEventHandler.send( SendAnalyticEvents.ConvertTokenButtonClicked( token = cryptoCurrency.symbol, blockchain = cryptoCurrency.network.name, ), ) - params.callback?.onConvertToAnotherToken(lastAmount = lastAmount) + params.callback?.onConvertToAnotherToken(lastAmount = lastAmount, isEnterInFiatSelected = isEnterInFiatSelected) } override fun resetSendNavigation() { @@ -455,7 +455,7 @@ internal class SendModel @Inject constructor( ) // If it is in active state use flow to update value in amount component modelScope.launch { - amount?.let { sendAmountUpdateTrigger.triggerUpdateAmount(it) } + amount?.let { sendAmountUpdateTrigger.triggerUpdateAmount(it, null) } } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt index 14068d2093..0dcbee1083 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountComponent.kt @@ -40,7 +40,7 @@ internal class SendAmountComponent( interface ModelCallback : NavigationModelCallback { fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean) - fun onConvertToAnotherToken(lastAmount: String) + fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) fun resetSendNavigation() fun onError(error: GetUserWalletError) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt index 1bd91ddafa..d3085ff9a2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt @@ -30,7 +30,7 @@ interface SendAmountReduceListener { * Different from another triggers because it takes raw string instead of BigDecimal */ interface SendAmountUpdateTrigger { - suspend fun triggerUpdateAmount(amountValue: String) + suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?) } /** @@ -38,7 +38,7 @@ interface SendAmountUpdateTrigger { * Different from another triggers because it takes raw string instead of BigDecimal */ interface SendAmountUpdateListener { - val updateAmountTriggerFlow: Flow + val updateAmountTriggerFlow: Flow> } @Singleton @@ -51,7 +51,7 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() : override val reduceToTriggerFlow = MutableSharedFlow() override val reduceByTriggerFlow = MutableSharedFlow() override val ignoreReduceTriggerFlow = MutableSharedFlow() - override val updateAmountTriggerFlow = MutableSharedFlow() + override val updateAmountTriggerFlow = MutableSharedFlow>() override suspend fun triggerReduceBy(reduceBy: ReduceByData) { reduceByTriggerFlow.emit(reduceBy) @@ -65,7 +65,7 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() : ignoreReduceTriggerFlow.emit(Unit) } - override suspend fun triggerUpdateAmount(amountValue: String) { - updateAmountTriggerFlow.emit(amountValue) + override suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?) { + updateAmountTriggerFlow.emit(amountValue to isEnterInFiatSelected) } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index edf9d9aa4f..3a215bc03b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -290,7 +290,15 @@ internal class SendAmountModel @Inject constructor( isPrimaryButtonEnabled = false, ) ?: it } - amountParams.callback.onConvertToAnotherToken(amountFieldData?.amountTextField?.value.orEmpty()) + + val isEnterInFiatSelected = amountFieldData?.amountTextField?.isFiatValue == true + val lastAmount = if (isEnterInFiatSelected) { + amountFieldData.amountTextField.fiatValue + } else { + amountFieldData?.amountTextField?.value + } + + amountParams.callback.onConvertToAnotherToken(lastAmount.orEmpty(), isEnterInFiatSelected) } private fun subscribeOnAmountReduceToTriggerUpdates() { @@ -346,7 +354,8 @@ internal class SendAmountModel @Inject constructor( private fun subscribeOnAmountUpdateTriggerUpdates() { sendAmountUpdateListener.updateAmountTriggerFlow - .onEach { amount -> + .onEach { (amount, isEnterInFiat) -> + isEnterInFiat?.let { onCurrencyChangeClick(isEnterInFiat) } onAmountValueChange(amount) saveResult() }.launchIn(modelScope) diff --git a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt index 20384b89c5..365495e6e5 100644 --- a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt +++ b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt @@ -16,6 +16,6 @@ interface SendWithSwapComponent : ComposableContentComponent { interface Factory : ComponentFactory interface ModelCallback { - fun onCloseSwap(lastAmount: String) + fun onCloseSwap(lastAmount: String, isEnterInFiatSelected: Boolean) } } \ No newline at end of file diff --git a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt index 5cec5c47df..7c9a0f4b91 100644 --- a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt +++ b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt @@ -5,7 +5,7 @@ package com.tangem.features.swap.v2.api.subcomponents * Different from another triggers because it takes raw string instead of BigDecimal */ interface SwapAmountUpdateTrigger { - suspend fun triggerUpdateAmount(amountValue: String) + suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean) suspend fun triggerQuoteReload() } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt index 3fe62bce62..33f79518f4 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt @@ -14,7 +14,7 @@ import javax.inject.Singleton * Different from another triggers because it takes raw string instead of BigDecimal */ interface SwapAmountUpdateListener { - val updateAmountTriggerFlow: Flow + val updateAmountTriggerFlow: Flow> val reloadQuotesTriggerFlow: Flow } @@ -44,8 +44,8 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() : SwapAmountReduceTrigger, SwapAmountReduceListener { - override val updateAmountTriggerFlow: SharedFlow - field = MutableSharedFlow() + override val updateAmountTriggerFlow: SharedFlow> + field = MutableSharedFlow>() override val reduceToTriggerFlow: SharedFlow field = MutableSharedFlow() @@ -59,8 +59,8 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() : override val reloadQuotesTriggerFlow: Flow field = MutableSharedFlow() - override suspend fun triggerUpdateAmount(amountValue: String) { - updateAmountTriggerFlow.emit(amountValue) + override suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean) { + updateAmountTriggerFlow.emit(amountValue to isEnterInFiatSelected) } override suspend fun triggerQuoteReload() { diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponent.kt index cb025a05e8..035c016356 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/SwapAmountComponent.kt @@ -46,7 +46,7 @@ internal class SwapAmountComponent @AssistedInject constructor( interface ModelCallback : NavigationModelCallback { fun onAmountResult(amountUM: SwapAmountUM) - fun onSeparatorClick(lastAmount: String) + fun onSeparatorClick(lastAmount: String, isEnterInFiatSelected: Boolean) fun resetSendWithSwapNavigation(resetNavigation: Boolean) } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index a6f5cf8206..53c158cd76 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -348,7 +348,10 @@ internal class SwapAmountModel @Inject constructor( private fun confirmSendWithSwapClose() { val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return - val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data + val amountField = uiState.value.swapDirection.withSwapDirection( + onDirect = { uiState.value.primaryAmount }, + onReverse = { uiState.value.secondaryAmount }, + ).amountField as? AmountState.Data val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus if (primaryCryptoCurrencyStatus != null) { @@ -367,7 +370,16 @@ internal class SwapAmountModel @Inject constructor( ) } - amountParams.callback.onSeparatorClick(lastAmount = amountFieldData?.amountTextField?.value.orEmpty()) + val isEnterInFiatSelected = amountField?.amountTextField?.isFiatValue == true + val lastAmount = if (isEnterInFiatSelected) { + amountField.amountTextField.fiatValue + } else { + amountField?.amountTextField?.value + } + amountParams.callback.onSeparatorClick( + lastAmount = lastAmount.orEmpty(), + isEnterInFiatSelected = isEnterInFiatSelected, + ) } private fun subscribeOnCryptoCurrencyStatusFlow() { @@ -407,9 +419,10 @@ internal class SwapAmountModel @Inject constructor( private fun subscribeOnAmountUpdateTriggerUpdates() { swapAmountUpdateListener.updateAmountTriggerFlow - .onEach { + .onEach { (amount, isEnterInFiat) -> if (uiState.value is SwapAmountUM.Content) { - onAmountValueChange(it) + onCurrencyChangeClick(isEnterInFiat) + onAmountValueChange(amount) saveResult() } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt index ede2387f9d..a92ea2bbff 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt @@ -110,8 +110,8 @@ internal class SendWithSwapModel @Inject constructor( uiState.update { it.copy(navigationUM = navigationUM) } } - override fun onSeparatorClick(lastAmount: String) { - params.callback?.onCloseSwap(lastAmount) + override fun onSeparatorClick(lastAmount: String, isEnterInFiatSelected: Boolean) { + params.callback?.onCloseSwap(lastAmount, isEnterInFiatSelected) router.popTo(initialRoute) } From 06be58d8837b60e40d3934350c075ea3ae79450c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 19:35:42 +0300 Subject: [PATCH 06/20] Updated on 2026-08-14 --- .../common/analytics/events/DetailsScreen.kt | 14 ------ .../analytics/TokenScreenAnalyticsEvent.kt | 22 +++++++-- .../tokendetails/model/TokenDetailsModel.kt | 45 +++++++++++++++++-- 3 files changed, 61 insertions(+), 20 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt deleted file mode 100644 index 775f44a0d8..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.tap.common.analytics.events - -import com.tangem.core.analytics.models.AnalyticsEvent - -/** -[REDACTED_AUTHOR] - */ -sealed class DetailsScreen( - event: String, - params: Map = mapOf(), -) : AnalyticsEvent("Details Screen", event, params) { - - class ScreenOpened : DetailsScreen("Details Screen Opened") -} \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index 54eac28f44..b33b3562ed 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -2,6 +2,7 @@ package com.tangem.domain.tokens.model.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION +import com.tangem.core.analytics.models.AnalyticsParam.Key.BALANCE import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM @@ -16,11 +17,26 @@ sealed class TokenScreenAnalyticsEvent( ) : AnalyticsEvent("Token", event, params) { /** Legacy event. It has a unique category, but it also is sent on TokenScreen */ - class DetailsScreenOpened(token: String) : AnalyticsEvent( + class DetailsScreenOpened( + blockchain: String, + token: String, + tokenBalance: TokenBalance, + ) : AnalyticsEvent( category = "Details Screen", event = "Details Screen Opened", - params = mapOf("Token" to token), - ) + params = mapOf( + BLOCKCHAIN to blockchain, + TOKEN_PARAM to token, + BALANCE to tokenBalance.name, + ), + ) { + sealed class TokenBalance(val name: String) { + data object Full : TokenBalance("Full") + data object Error : TokenBalance("Error") + data object Empty : TokenBalance("Empty") + data object NoAddress : TokenBalance("No Address") + } + } class ButtonRemoveToken(token: String) : TokenScreenAnalyticsEvent( "Button - Remove Token", diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index d0e4d0f29c..7a368fe134 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -62,6 +62,7 @@ import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText +import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.DetailsScreenOpened.TokenBalance import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.transaction.error.AssociateAssetError import com.tangem.domain.transaction.error.IncompleteTransactionError @@ -90,6 +91,7 @@ import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter import com.tangem.utils.Provider import com.tangem.utils.coroutines.* +import com.tangem.utils.extensions.isZero import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.async @@ -162,6 +164,7 @@ internal class TokenDetailsModel @Inject constructor( private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null + private var isBalanceLoadedEventSent = false private var expressTxStatusTaskScheduler = SingleTaskScheduler>() /** Transaction id to check for status */ @@ -207,9 +210,6 @@ internal class TokenDetailsModel @Inject constructor( val uiState: StateFlow = internalUiState init { - analyticsEventsHandler.send( - event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), - ) updateTopBarMenu() initButtons() updateContent() @@ -326,6 +326,7 @@ internal class TokenDetailsModel @Inject constructor( .onEach { maybeCurrencyStatus -> internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) maybeCurrencyStatus.onRight { status -> + sendOneTimeBalanceLoadedAnalyticsEvent(status) cryptoCurrencyStatus = status updateButtons(currencyStatus = status) updateWarnings(status) @@ -1122,6 +1123,44 @@ internal class TokenDetailsModel @Inject constructor( ) } + private fun sendOneTimeBalanceLoadedAnalyticsEvent(cryptoCurrencyStatus: CryptoCurrencyStatus?) { + if (isBalanceLoadedEventSent || cryptoCurrencyStatus == null) return + + val tokenBalance = when (val value = cryptoCurrencyStatus.value) { + is CryptoCurrencyStatus.Custom, + is CryptoCurrencyStatus.Loaded, + is CryptoCurrencyStatus.NoQuote, + -> { + if (value.sources.networkSource.isActual()) { + val amount = value.amount + if (amount != null && !amount.isZero()) { + TokenBalance.Full + } else { + TokenBalance.Empty + } + } else { + return + } + } + is CryptoCurrencyStatus.NoAccount, + is CryptoCurrencyStatus.MissedDerivation, + -> TokenBalance.NoAddress + + is CryptoCurrencyStatus.NoAmount, + is CryptoCurrencyStatus.Unreachable, + -> TokenBalance.Error + CryptoCurrencyStatus.Loading -> return + } + analyticsEventsHandler.send( + event = TokenScreenAnalyticsEvent.DetailsScreenOpened( + blockchain = cryptoCurrency.network.name, + token = cryptoCurrency.symbol, + tokenBalance = tokenBalance, + ), + ) + isBalanceLoadedEventSent = true + } + private companion object { const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L } From 5888982e93992cced99a4ee98667f0856685ab5c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 09:32:49 +0300 Subject: [PATCH 07/20] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index bd1f7e6e67..ac2c36e249 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -17,7 +17,7 @@ }, { "name": "STAKING_CARDANO_ENABLED", - "version": "5.28.0" + "version": "undefined" }, { "name": "WALLET_CONNECT_REDESIGN_ENABLED", From 0c9e739c7f4ccc56389b3d849affb3cbb26509fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Sep 2025 18:50:43 +0500 Subject: [PATCH 08/20] Updated on 2026-08-14 --- .../walletconnect/pair/WcPairSdkDelegate.kt | 5 ++-- .../domain/walletconnect/model/WcPairError.kt | 23 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt index 35902fb41f..3c57748b9e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt @@ -69,8 +69,7 @@ internal class WcPairSdkDelegate : WcSdkObserver { ifRight = { result -> when (result) { is Wallet.Model.SettledSessionResponse.Result -> result.right() - is Wallet.Model.SettledSessionResponse.Error -> - ApprovalFailed(result.errorMessage).left() + is Wallet.Model.SettledSessionResponse.Error -> ApprovalFailed(result.errorMessage).left() } }, ) @@ -147,7 +146,7 @@ internal class WcPairSdkDelegate : WcSdkObserver { else -> WcPairError.PairingFailed(this.localizedMessage.orEmpty()) } - private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left() + private fun Throwable.toApproveError() = ApprovalFailed(this.localizedMessage.orEmpty()).left() companion object { private const val CALLBACK_TIMEOUT = 15 diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt index 2ac1b68531..b93bf1fd70 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcPairError.kt @@ -5,16 +5,17 @@ sealed class WcPairError( override val message: String = "", ) : Exception() { - data class UriAlreadyUsed(override val message: String) : WcPairError("107 001 001") - data class PairingFailed(override val message: String) : WcPairError("107 001 002") - data object InvalidDomainURL : WcPairError("107 001 003") - data class UnsupportedDApp(val appName: String) : WcPairError("107 001 004") - data class UnsupportedBlockchains(val chains: Set, val appName: String) : WcPairError("107 001 005") + data class UriAlreadyUsed(override val message: String) : WcPairError(code = "107 001 001") + data class PairingFailed(override val message: String) : WcPairError(code = "107 001 002") + data object InvalidDomainURL : WcPairError(code = "107 001 003") - data object InvalidConnectionRequest : WcPairError("107 002 001") - data object ProposalExpired : WcPairError("107 002 002") - data class ApprovalFailed(override val message: String) : WcPairError("107 002 003") - data object RejectionFailed : WcPairError("107 002 004") - data class Unknown(override val message: String) : WcPairError(message) - data class TimeoutException(override val message: String) : WcPairError(message) + data class UnsupportedDApp(val appName: String) : WcPairError(code = "107 001 004") + data class UnsupportedBlockchains(val chains: Set, val appName: String) : WcPairError(code = "107 001 005") + + data object InvalidConnectionRequest : WcPairError(code = "107 002 001") + data object ProposalExpired : WcPairError(code = "107 002 002") + data class ApprovalFailed(override val message: String) : WcPairError(code = "107 002 003") + data object RejectionFailed : WcPairError(code = "107 002 004") + data class Unknown(override val message: String) : WcPairError(code = "107 003 001") + data class TimeoutException(override val message: String) : WcPairError(code = "107 003 002") } \ No newline at end of file From 843a192a82921582a97a61418db228f48bed48a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 20:30:44 +0500 Subject: [PATCH 09/20] Updated on 2026-08-14 --- .../pair/DefaultWcPairUseCase.kt | 7 +++++- .../walletconnect/pair/WcPairSdkDelegate.kt | 3 ++- .../request/DefaultWcRequestService.kt | 7 +++++- .../sessions/DefaultWcSessionsManager.kt | 20 ++++++++++++++--- .../utils/WcAppMetaDataConverter.kt | 22 ++++++++++--------- .../utils/WcSdkSessionConverter.kt | 21 +++++++++++++----- .../utils/WcSdkSessionRequestConverter.kt | 22 ++++++++++++++----- .../walletconnect/DefaultWcPairUseCaseTest.kt | 20 +++++++++++------ .../walletconnect/model/WcSessionDTO.kt | 1 + 9 files changed, 89 insertions(+), 34 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index ad67584bed..0ad54408ee 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -100,7 +100,12 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( ).map { settledSession -> val newSession = WcSession( wallet = sessionForApprove.wallet, - sdkModel = WcSdkSessionConverter.convert(settledSession.session), + sdkModel = WcSdkSessionConverter.convert( + value = WcSdkSessionConverter.Input( + originUrl = sdkVerifyContext.origin, + session = settledSession.session, + ), + ), securityStatus = proposalState.dAppSession.securityStatus, networks = sessionForApprove.network.toSet(), connectingTime = DateTime.now().millis, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt index 3c57748b9e..85ebda3168 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt @@ -112,8 +112,9 @@ internal class WcPairSdkDelegate : WcSdkObserver { sessionProposal: Wallet.Model.SessionProposal, verifyContext: Wallet.Model.VerifyContext, ) { + val sessionProposalWithRealUrl = sessionProposal.copy(url = verifyContext.origin) // Triggered when wallet receives the session proposal sent by a Dapp - onSessionProposal.trySend(sessionProposal to verifyContext) + onSessionProposal.trySend(sessionProposalWithRealUrl to verifyContext) } override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index db8788c05a..92e8b87959 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -26,7 +26,12 @@ internal class DefaultWcRequestService( verifyContext: Wallet.Model.VerifyContext, ) { // Triggered when a Dapp sends SessionRequest to sign a transaction or a message - val sr = WcSdkSessionRequestConverter.convert(sessionRequest) + val sr = WcSdkSessionRequestConverter.convert( + WcSdkSessionRequestConverter.Input( + originUrl = verifyContext.origin, + sessionRequest = sessionRequest, + ), + ) Timber.tag(WC_TAG).i("handle request $sr") val name = requestConverters.firstNotNullOfOrNull { it.toWcMethodName(sr) } ?: WcMethodName.Unsupported(sr.request.method) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index 38ed978e2d..69aa56551c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -70,6 +70,7 @@ internal class DefaultWcSessionsManager( WcSessionDTO( topic = session.sdkModel.topic, walletId = session.wallet.walletId, + url = session.sdkModel.appMetaData.url, securityStatus = session.securityStatus, connectingTime = session.connectingTime ?: DateTime.now().millis, ), @@ -105,8 +106,15 @@ internal class DefaultWcSessionsManager( .map { walletId -> flow { emit( - legacyStore.loadSessions(walletId.stringValue).map { - WcSessionDTO(it.topic, walletId, CheckDAppResult.FAILED_TO_VERIFY) + legacyStore.loadSessions(walletId.stringValue).mapNotNull { legacySession -> + val url = + inSdk.find { it.topic == legacySession.topic }?.metaData?.url ?: return@mapNotNull null + WcSessionDTO( + topic = legacySession.topic, + walletId = walletId, + url = url, + securityStatus = CheckDAppResult.FAILED_TO_VERIFY, + ) }, ) } @@ -130,9 +138,15 @@ internal class DefaultWcSessionsManager( val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null val networks = wcNetworksConverter.findWalletNetworks(wallet, sdkSession) + val originUrl = storeSession.url ?: sdkSession.metaData?.url ?: "" WcSession( wallet = wallet, - sdkModel = WcSdkSessionConverter.convert(sdkSession), + sdkModel = WcSdkSessionConverter.convert( + value = WcSdkSessionConverter.Input( + originUrl = originUrl, + session = sdkSession, + ), + ), securityStatus = storeSession.securityStatus, networks = networks, connectingTime = storeSession.connectingTime, diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcAppMetaDataConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcAppMetaDataConverter.kt index df4fdcaade..3ae419705d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcAppMetaDataConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcAppMetaDataConverter.kt @@ -4,7 +4,7 @@ import com.reown.android.Core import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData import com.tangem.utils.converter.Converter -internal object WcAppMetaDataConverter : Converter { +internal object WcAppMetaDataConverter : Converter { val empty get() = WcAppMetaData( @@ -18,16 +18,18 @@ internal object WcAppMetaDataConverter : Converter { +internal object WcSdkSessionConverter : Converter { - override fun convert(value: Wallet.Model.Session): WcSdkSession { + override fun convert(value: Input): WcSdkSession { return WcSdkSession( - topic = value.topic, - appMetaData = value.metaData?.let { WcAppMetaDataConverter.convert(it) } ?: WcAppMetaDataConverter.empty, - namespaces = value.namespaces.mapValues { (_, session) -> + topic = value.session.topic, + appMetaData = value.session.metaData + ?.let { + WcAppMetaDataConverter.convert( + value = WcAppMetaDataConverter.Input( + originUrl = value.originUrl, + peerMetaData = it, + ), + ) + } + ?: WcAppMetaDataConverter.empty, + namespaces = value.session.namespaces.mapValues { (_, session) -> WcSdkSession.Session( chains = session.chains ?: listOf(), accounts = session.accounts, @@ -20,4 +29,6 @@ internal object WcSdkSessionConverter : Converter { +internal object WcSdkSessionRequestConverter : Converter { - override fun convert(value: Wallet.Model.SessionRequest): WcSdkSessionRequest { + override fun convert(value: Input): WcSdkSessionRequest { return WcSdkSessionRequest( - topic = value.topic, - chainId = value.chainId, - dAppMetaData = value.peerMetaData?.let { WcAppMetaDataConverter.convert(it) } + topic = value.sessionRequest.topic, + chainId = value.sessionRequest.chainId, + dAppMetaData = value.sessionRequest.peerMetaData + ?.let { + WcAppMetaDataConverter.convert( + value = WcAppMetaDataConverter.Input( + originUrl = value.originUrl, + peerMetaData = it, + ), + ) + } ?: WcAppMetaDataConverter.empty, - request = JSONRPCRequestConverter.convert(value.request), + request = JSONRPCRequestConverter.convert(value.sessionRequest.request), ) } + + internal data class Input(val originUrl: String, val sessionRequest: Wallet.Model.SessionRequest) } \ No newline at end of file diff --git a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt index 573d2949bc..b244486335 100644 --- a/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt +++ b/data/wallet-connect/src/test/kotlin/com/tangem/domain/walletconnect/DefaultWcPairUseCaseTest.kt @@ -60,7 +60,7 @@ internal class DefaultWcPairUseCaseTest { private val sdkVerifyContext: Wallet.Model.VerifyContext get() = Wallet.Model.VerifyContext( id = 9259, - origin = "vituperata", + origin = "https://react-app.walletconnect.com/", validation = Wallet.Model.Validation.VALID, verifyUrl = "https://search.yahoo.com/search?p=id", isScam = false, @@ -68,6 +68,7 @@ internal class DefaultWcPairUseCaseTest { private val unsupportedDAppUrl = "dydx.trade" private val unsupportedSdkProposal get() = sdkProposal.copy(url = unsupportedDAppUrl) + private val unsupportedSdkVerifyContext get() = sdkVerifyContext.copy(origin = unsupportedDAppUrl) private val sessionForApprove: WcSessionApprove get() = WcSessionApprove( @@ -100,7 +101,12 @@ internal class DefaultWcPairUseCaseTest { private val Wallet.Model.Session.sessionForSave: WcSession get() = WcSession( wallet = sessionForApprove.wallet, - sdkModel = WcSdkSessionConverter.convert(this), + sdkModel = WcSdkSessionConverter.convert( + value = WcSdkSessionConverter.Input( + originUrl = sdkVerifyContext.origin, + session = this, + ), + ), securityStatus = CheckDAppResult.SAFE, networks = setOf(), connectingTime = null, @@ -138,7 +144,7 @@ internal class DefaultWcPairUseCaseTest { assertEquals(loading, awaitItem()) coVerifyOrder { sdkDelegate.pair(url) - blockAidVerifier.verifyDApp(DAppData(sdkProposal.url)) + blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin)) } assert(awaitItem() is WcPairState.Proposal) expectNoEvents() @@ -161,7 +167,7 @@ internal class DefaultWcPairUseCaseTest { assertEquals(loading, awaitItem()) coVerifyOrder { sdkDelegate.pair(url) - blockAidVerifier.verifyDApp(DAppData(sdkProposal.url)) + blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin)) } assert(awaitItem() is WcPairState.Proposal) useCase.approve(sessionForApprove) @@ -194,7 +200,7 @@ internal class DefaultWcPairUseCaseTest { assertEquals(loading, awaitItem()) coVerifyOrder { sdkDelegate.pair(url) - blockAidVerifier.verifyDApp(DAppData(sdkProposal.url)) + blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin)) } assert(awaitItem() is WcPairState.Proposal) useCase.reject() @@ -207,7 +213,7 @@ internal class DefaultWcPairUseCaseTest { @Test fun `success pair and reject unsupported dApp`() = runTest { - coEvery { sdkDelegate.pair(url) } returns (unsupportedSdkProposal to sdkVerifyContext).right() + coEvery { sdkDelegate.pair(url) } returns (unsupportedSdkProposal to unsupportedSdkVerifyContext).right() val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDApp(unsupportedSdkProposal.name)) val useCase = useCaseFactory() @@ -255,7 +261,7 @@ internal class DefaultWcPairUseCaseTest { coVerifyOrder { sdkDelegate.pair(url) associateNetworksDelegate.associate(sdkProposal) - blockAidVerifier.verifyDApp(DAppData(sdkProposal.url)) + blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin)) } assert(awaitItem() is WcPairState.Proposal) useCase.approve(sessionForApprove) diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionDTO.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionDTO.kt index 7feba71451..572d3aeb9a 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionDTO.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcSessionDTO.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId data class WcSessionDTO( val topic: String, val walletId: UserWalletId, + val url: String?, val securityStatus: CheckDAppResult = CheckDAppResult.FAILED_TO_VERIFY, val connectingTime: Long? = null, ) \ No newline at end of file From f5ff6a845354e7384e24b7fd90039ae64fefabfe Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 23:45:30 +0500 Subject: [PATCH 10/20] Updated on 2026-08-14 --- .../pair/DefaultWcPairUseCase.kt | 5 +-- .../sign/WcSignUseCaseDelegate.kt | 1 + .../domain/walletconnect/WcAnalyticEvents.kt | 33 ++++++++++++------- .../transaction/model/WcAddNetworkModel.kt | 1 + .../model/WcSendTransactionModel.kt | 1 + .../model/WcSignTransactionModel.kt | 1 + 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt index 0ad54408ee..522abbb6e0 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/DefaultWcPairUseCase.kt @@ -114,8 +114,9 @@ internal class DefaultWcPairUseCase @AssistedInject constructor( sessionsManager.saveSession(newSession) analytics.send( WcAnalyticEvents.DAppConnected( - proposalState.dAppSession, - sessionForApprove, + sessionProposal = proposalState.dAppSession, + sessionForApprove = sessionForApprove, + securityStatus = proposalState.dAppSession.securityStatus, ), ) newSession diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt index 73dc766bbb..814e12788d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/WcSignUseCaseDelegate.kt @@ -75,6 +75,7 @@ internal class WcSignUseCaseDelegate( WcAnalyticEvents.SignatureRequestHandled( rawRequest = context.rawSdkRequest, network = context.network, + securityStatus = context.session.securityStatus, ) }, ) diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index 8b6bd34eae..064f349412 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -5,6 +5,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult.* import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.WcAnalyticEvents.ButtonDisconnectAll.toAnalyticVerificationStatus import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.WcSession import com.tangem.domain.walletconnect.model.WcSessionApprove @@ -42,11 +43,7 @@ sealed class WcAnalyticEvents( event = "dApp Connection Requested", params = mapOf( NETWORKS to network.joinToString(",") { it.name }, - DOMAIN_VERIFICATION to when (domainVerification) { - SAFE -> "Verified" - UNSAFE -> "Risky" - FAILED_TO_VERIFY -> "Unknown" - }, + DOMAIN_VERIFICATION to domainVerification.toAnalyticVerificationStatus(), ), ) @@ -62,12 +59,14 @@ sealed class WcAnalyticEvents( class DAppConnected( sessionProposal: WcSessionProposal, sessionForApprove: WcSessionApprove, + securityStatus: CheckDAppResult, ) : WcAnalyticEvents( event = "dApp Connected", params = mapOf( AnalyticsParam.Key.DAPP_NAME to sessionProposal.dAppMetaData.name, AnalyticsParam.Key.DAPP_URL to sessionProposal.dAppMetaData.url, AnalyticsParam.Key.BLOCKCHAIN to sessionForApprove.network.joinToString(",") { it.name }, + DOMAIN_VERIFICATION to securityStatus.toAnalyticVerificationStatus(), ), ) @@ -105,6 +104,7 @@ sealed class WcAnalyticEvents( rawRequest: WcSdkSessionRequest, network: Network, emulationStatus: EmulationStatus?, + securityStatus: CheckDAppResult?, ) : WcAnalyticEvents( event = "Signature Request Received", params = mapOf( @@ -113,6 +113,7 @@ sealed class WcAnalyticEvents( AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, AnalyticsParam.Key.EMULATION_STATUS to emulationStatus?.status, + AnalyticsParam.Key.TYPE to securityStatus?.toAnalyticVerificationStatus(), ).mapNotNullValues { it.value }, ) { enum class EmulationStatus(val status: String) { @@ -125,6 +126,7 @@ sealed class WcAnalyticEvents( class SignatureRequestHandled( rawRequest: WcSdkSessionRequest, network: Network, + securityStatus: CheckDAppResult, ) : WcAnalyticEvents( event = "Signature Request Handled", params = mapOf( @@ -132,6 +134,7 @@ sealed class WcAnalyticEvents( AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url, AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method, AnalyticsParam.Key.BLOCKCHAIN to network.name, + AnalyticsParam.Key.TYPE to securityStatus.toAnalyticVerificationStatus(), ), ) @@ -215,18 +218,26 @@ sealed class WcAnalyticEvents( Source.Domain -> "Domain" Source.SmartContract -> "Smart Contract" }, - AnalyticsParam.Key.TYPE to when (securityStatus) { - SAFE -> "Verified" - UNSAFE -> "Risky" - FAILED_TO_VERIFY -> "Unknown" - }, - + AnalyticsParam.Key.TYPE to securityStatus.toAnalyticVerificationStatus(), ), ) { enum class Source { Domain, SmartContract } } + enum class DAppVerificationStatus(val status: String) { + Verified("Verified"), + Risky("Risky"), + Unknown("Unknown"), + } + + fun CheckDAppResult.toAnalyticVerificationStatus(): String = when (this) { + SAFE -> DAppVerificationStatus.Verified + UNSAFE -> DAppVerificationStatus.Risky + FAILED_TO_VERIFY -> DAppVerificationStatus.Unknown + }.status + companion object { + const val NETWORKS = "Networks" const val DOMAIN_VERIFICATION = "Domain Verification" const val WC_CATEGORY_NAME = "Wallet Connect" diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index ab284df37a..e15ce42f95 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -132,6 +132,7 @@ internal class WcAddNetworkModel @Inject constructor( rawRequest = useCase.rawSdkRequest, network = useCase.network, emulationStatus = null, + securityStatus = null, ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index d3f10b517e..ebda2cb59a 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -408,6 +408,7 @@ internal class WcSendTransactionModel @Inject constructor( rawRequest = useCase.rawSdkRequest, network = useCase.network, emulationStatus = emulationStatus, + securityStatus = useCase.session.securityStatus, ), ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt index 9f3a686357..28b294640d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSignTransactionModel.kt @@ -158,6 +158,7 @@ internal class WcSignTransactionModel @Inject constructor( rawRequest = useCase.rawSdkRequest, network = useCase.network, emulationStatus = null, + securityStatus = null, ), ) From 7f950708d7a7e923a80ce475a33713f62f0160c0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Sep 2025 18:17:29 +0400 Subject: [PATCH 11/20] Updated on 2026-08-14 --- .../model/MultiWalletFinalizeModel.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index 0c7b8d6290..8565d7c6f3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -7,21 +7,22 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.isRing +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent @@ -53,6 +54,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, private val userWalletsListManager: UserWalletsListManager, private val saveWalletUseCase: SaveWalletUseCase, + private val updateWalletUseCase: UpdateWalletUseCase, private val cardRepository: CardRepository, private val onboardingRepository: OnboardingRepository, private val walletsRepository: WalletsRepository, @@ -249,11 +251,13 @@ internal class MultiWalletFinalizeModel @Inject constructor( } ?: userWalletCreated - saveWalletUseCase( - userWallet = userWallet.requireColdWallet().copy( - scanResponse = scanResponse.updateScanResponseAfterBackup(), - ), - canOverride = true, + updateWalletUseCase( + userWalletId = userWallet.walletId, + update = { + it.requireColdWallet().copy( + scanResponse = scanResponse.updateScanResponseAfterBackup(), + ) + }, ) userWallet From f80ce6c1752334791f11eb28f6acdb6b4406eb58 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 11:46:59 +0500 Subject: [PATCH 12/20] Updated on 2026-08-14 --- .../settings/DefaultSettingsRepository.kt | 11 +++++++- .../settings/IsSendTapHelpEnabledUseCase.kt | 4 ++- .../repositories/SettingsRepository.kt | 5 +++- .../v2/feeselector/model/FeeSelectorModel.kt | 7 ++++- .../v2/send/confirm/model/SendConfirmModel.kt | 27 +++++++++---------- .../confirm/model/NFTSendConfirmModel.kt | 21 ++++++++------- .../confirm/model/SendWithSwapConfirmModel.kt | 13 ++++++++- 7 files changed, 60 insertions(+), 28 deletions(-) diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index 8bc077f7c7..e27fce119a 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -5,12 +5,14 @@ import com.tangem.datasource.api.tangemTech.models.GeoResponse import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.store import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.models.GB_COUNTRY import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext @@ -56,7 +58,14 @@ internal class DefaultSettingsRepository( appLogsStore.deleteDeprecatedLogs(maxSize) } - override suspend fun isSendTapHelpPreviewEnabled(): Boolean { + override fun isSendTapHelpPreviewEnabled(): Flow { + return appPreferencesStore.get( + key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY, + default = true, + ) + } + + override suspend fun isSendTapHelpPreviewEnabledSync(): Boolean { return appPreferencesStore.getSyncOrDefault( key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY, default = true, diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsSendTapHelpEnabledUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsSendTapHelpEnabledUseCase.kt index cb59249538..f68900ff1e 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/IsSendTapHelpEnabledUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/IsSendTapHelpEnabledUseCase.kt @@ -10,5 +10,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository */ class IsSendTapHelpEnabledUseCase(private val settingsRepository: SettingsRepository) { - suspend operator fun invoke() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabled() } + operator fun invoke() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabled() } + + suspend fun invokeSync() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabledSync() } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index dd8608db95..e39efd87a2 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -1,6 +1,7 @@ package com.tangem.domain.settings.repositories import com.tangem.domain.settings.usercountry.models.UserCountry +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @Suppress("TooManyFunctions") @@ -16,7 +17,9 @@ interface SettingsRepository { fun deleteDeprecatedLogs(maxSize: Int) - suspend fun isSendTapHelpPreviewEnabled(): Boolean + fun isSendTapHelpPreviewEnabled(): Flow + + suspend fun isSendTapHelpPreviewEnabledSync(): Boolean suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt index 2784044d44..7e9b481bef 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt @@ -13,6 +13,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.settings.NeverShowTapHelpUseCase import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted @@ -46,6 +47,7 @@ internal class FeeSelectorModel @Inject constructor( private val urlOpener: UrlOpener, private val isFeeApproximateUseCase: IsFeeApproximateUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, private val feeSelectorReloadListener: FeeSelectorReloadListener, private val feeSelectorCheckReloadListener: FeeSelectorCheckReloadListener, private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger, @@ -185,7 +187,10 @@ internal class FeeSelectorModel @Inject constructor( source = SendScreenSource.Fee, ), ) - feeSelectorBottomSheet.activate(Unit) + modelScope.launch { + neverShowTapHelpUseCase() + feeSelectorBottomSheet.activate(Unit) + } } private fun subscribeOnFeeReloadTriggerUpdates() { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 5fcc8c9505..c67ed750cb 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -20,6 +20,7 @@ import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase @@ -28,7 +29,6 @@ import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.requireColdWallet -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase import com.tangem.domain.settings.NeverShowTapHelpUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase @@ -170,6 +170,7 @@ internal class SendConfirmModel @Inject constructor( subscribeOnCheckFeeResultUpdates() initialState() subscribeOnBalanceHidden() + subscribeOnTapHelpUpdates() } fun updateState(state: SendUM) { @@ -234,10 +235,6 @@ internal class SendConfirmModel @Inject constructor( override fun showEditDestination() { modelScope.launch { neverShowTapHelpUseCase() - _uiState.update { - val confirmUM = it.confirmUM as? ConfirmUM.Content - it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM) - } analyticsEventHandler.send( CommonSendAnalyticEvents.ScreenReopened( categoryName = analyticsCategoryName, @@ -251,10 +248,6 @@ internal class SendConfirmModel @Inject constructor( override fun showEditAmount() { modelScope.launch { neverShowTapHelpUseCase() - _uiState.update { - val confirmUM = it.confirmUM as? ConfirmUM.Content - it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM) - } analyticsEventHandler.send( CommonSendAnalyticEvents.ScreenReopened( categoryName = analyticsCategoryName, @@ -268,10 +261,6 @@ internal class SendConfirmModel @Inject constructor( override fun showEditFee() { modelScope.launch { neverShowTapHelpUseCase() - _uiState.update { - val confirmUM = it.confirmUM as? ConfirmUM.Content - it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM) - } analyticsEventHandler.send( CommonSendAnalyticEvents.ScreenReopened( categoryName = analyticsCategoryName, @@ -356,7 +345,7 @@ internal class SendConfirmModel @Inject constructor( val confirmUM = uiState.value.confirmUM modelScope.launch { - val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false } + val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false } if (confirmUM is ConfirmUM.Empty) { _uiState.update { it.copy( @@ -372,6 +361,16 @@ internal class SendConfirmModel @Inject constructor( } } + private fun subscribeOnTapHelpUpdates() { + isSendTapHelpEnabledUseCase().getOrNull() + ?.onEach { showTapHelp -> + _uiState.update { + val confirmUM = it.confirmUM as? ConfirmUM.Content + it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM) + } + }?.launchIn(modelScope) + } + private fun subscribeOnNotificationsUpdateTrigger() { notificationsUpdateListener.hasErrorFlow .onEach { hasError -> diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index 0cd5ed9676..3d3f4d15fe 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -138,6 +138,7 @@ internal class NFTSendConfirmModel @Inject constructor( configConfirmNavigation() subscribeOnNotificationsUpdateTrigger() subscribeOnCheckFeeResultUpdates() + subscribeOnTapHelpUpdates() initialState() } @@ -178,10 +179,6 @@ internal class NFTSendConfirmModel @Inject constructor( override fun showEditDestination() { modelScope.launch { neverShowTapHelpUseCase() - _uiState.update { - val confirmUM = it.confirmUM as? ConfirmUM.Content - it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM) - } analyticsEventHandler.send( CommonSendAnalyticEvents.ScreenReopened( categoryName = analyticsCategoryName, @@ -195,10 +192,6 @@ internal class NFTSendConfirmModel @Inject constructor( override fun showEditFee() { modelScope.launch { neverShowTapHelpUseCase() - _uiState.update { - val confirmUM = it.confirmUM as? ConfirmUM.Content - it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM) - } analyticsEventHandler.send( CommonSendAnalyticEvents.ScreenReopened( categoryName = analyticsCategoryName, @@ -270,7 +263,7 @@ internal class NFTSendConfirmModel @Inject constructor( } modelScope.launch { - val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false } + val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false } if (confirmUM is ConfirmUM.Empty || isEmptyFee) { _uiState.update { it.copy( @@ -285,6 +278,16 @@ internal class NFTSendConfirmModel @Inject constructor( } } + private fun subscribeOnTapHelpUpdates() { + isSendTapHelpEnabledUseCase().getOrNull() + ?.onEach { showTapHelp -> + _uiState.update { + val confirmUM = it.confirmUM as? ConfirmUM.Content + it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM) + } + }?.launchIn(modelScope) + } + private fun subscribeOnNotificationsUpdateTrigger() { notificationsUpdateListener.hasErrorFlow .onEach { hasError -> diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index a434134267..e480cd76c5 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -142,6 +142,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( configConfirmNavigation() initialState() subscribeOnNotificationUpdates() + subscribeOnTapHelpUpdates() } override fun onFeeResult(feeSelectorUM: FeeSelectorUM) { @@ -313,7 +314,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( val confirmUM = uiState.value.confirmUM modelScope.launch { - val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false } + val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false } if (confirmUM is ConfirmUM.Empty) { uiState.update { it.copy( @@ -327,6 +328,16 @@ internal class SendWithSwapConfirmModel @Inject constructor( } } + private fun subscribeOnTapHelpUpdates() { + isSendTapHelpEnabledUseCase().getOrNull() + ?.onEach { showTapHelp -> + uiState.update { + val confirmUM = it.confirmUM as? ConfirmUM.Content + it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM) + } + }?.launchIn(modelScope) + } + private fun updateConfirmNotifications() { modelScope.launch { sendNotificationsUpdateTrigger.triggerUpdate( From 74a746b237574c9c6823f54a13c29132c96d35ac Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 11:31:02 +0200 Subject: [PATCH 13/20] Updated on 2026-08-14 --- .../component/DefaultTokenReceiveComponent.kt | 5 +- .../component/TokenReceiveAssetsComponent.kt | 7 +- .../component/TokenReceiveQrCodeComponent.kt | 3 +- .../entity/TokenReceiveStateFactory.kt | 156 ++++++++++++++++++ .../model/TokenReceiveAssetsModel.kt | 4 +- .../tokenreceive/model/TokenReceiveModel.kt | 150 ++--------------- .../model/TokenReceiveQrCodeModel.kt | 2 +- .../tokenreceive/route/TokenReceiveRoutes.kt | 2 +- .../ui/TokenReceiveAssetsContent.kt | 32 ++-- .../tokenreceive/ui/state/ReceiveAssetsUM.kt | 7 +- .../tokenreceive/ui/state/TokenReceiveUM.kt | 3 +- 11 files changed, 204 insertions(+), 167 deletions(-) create mode 100644 features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/entity/TokenReceiveStateFactory.kt diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/DefaultTokenReceiveComponent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/DefaultTokenReceiveComponent.kt index a6914ac4c4..20f139ff39 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/DefaultTokenReceiveComponent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/DefaultTokenReceiveComponent.kt @@ -78,10 +78,11 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor( appComponentContext = appComponentContext, params = TokenReceiveQrCodeComponent.TokenReceiveQrCodeParams( cryptoCurrency = model.params.config.cryptoCurrency, - address = model.state.value.addresses[config.addressId] ?: error("Address has to be there"), + address = model.state.value.addresses.find { it.value == config.address } ?: error( + "Address has to be there", + ), callback = model, onDismiss = ::dismiss, - id = config.addressId, ), ) TokenReceiveRoutes.ReceiveAssets -> TokenReceiveAssetsComponent( diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveAssetsComponent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveAssetsComponent.kt index 692e3ae990..a292fe45db 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveAssetsComponent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveAssetsComponent.kt @@ -13,7 +13,6 @@ import com.tangem.features.tokenreceive.entity.ReceiveAddress import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.ImmutableMap internal class TokenReceiveAssetsComponent( appComponentContext: AppComponentContext, @@ -29,13 +28,13 @@ internal class TokenReceiveAssetsComponent( } internal interface TokenReceiveAssetsModelCallback { - fun onQrCodeClick(id: Int) - fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource) + fun onQrCodeClick(address: String) + fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource) } data class TokenReceiveAssetsParams( val notificationConfigs: ImmutableList, - val addresses: ImmutableMap, + val addresses: ImmutableList, val callback: TokenReceiveAssetsModelCallback, val onDismiss: () -> Unit, val showMemoDisclaimer: Boolean, diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveQrCodeComponent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveQrCodeComponent.kt index c33bcca97f..02123bbf3a 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveQrCodeComponent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/component/TokenReceiveQrCodeComponent.kt @@ -27,12 +27,11 @@ internal class TokenReceiveQrCodeComponent( } internal interface TokenReceiveQrCodeModelCallback { - fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource) + fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource) fun onShareClick(address: String) } data class TokenReceiveQrCodeParams( - val id: Int, val cryptoCurrency: CryptoCurrency, val address: ReceiveAddress, val callback: TokenReceiveQrCodeModelCallback, diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/entity/TokenReceiveStateFactory.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/entity/TokenReceiveStateFactory.kt new file mode 100644 index 0000000000..5747409661 --- /dev/null +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/entity/TokenReceiveStateFactory.kt @@ -0,0 +1,156 @@ +package com.tangem.features.tokenreceive.entity + +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.R +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.models.ReceiveAddressModel +import com.tangem.domain.models.TokenReceiveNotification +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.ens.EnsAddress +import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens +import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Primary +import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM +import com.tangem.utils.Provider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList + +internal class TokenReceiveStateFactory( + private val currentStateProvider: Provider, + private val cryptoCurrency: CryptoCurrency, + private val addresses: List, + private val tokenReceiveNotification: List, +) { + + private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) + + fun getInitialState(tokenName: String): TokenReceiveUM { + return TokenReceiveUM( + addresses = mapAddresses( + addresses = addresses, + cryptoCurrency = cryptoCurrency, + ), + iconState = iconStateConverter.convert(cryptoCurrency), + network = cryptoCurrency.network.name, + isEnsResultLoading = false, + notificationConfigs = getNotifications( + tokenName = tokenName, + tokenReceiveNotification = tokenReceiveNotification, + ).toImmutableList(), + ) + } + + fun getLoadingState(): TokenReceiveUM { + return currentStateProvider.invoke().copy(isEnsResultLoading = true) + } + + fun getLoadedState(ensAddress: List): TokenReceiveUM { + val currentAddressValues = currentStateProvider.invoke().addresses.map { it.value }.toSet() + + val newEnsAddresses = ensAddress + .filterIsInstance() + .filterNot { it.name in currentAddressValues } + .map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) } + + val combinedAddresses = (currentStateProvider.invoke().addresses + newEnsAddresses) + .sortedWith( + compareBy { address -> + when (address.type) { + is Ens -> 0 + is Primary.Default -> 1 + is Primary.Legacy -> 2 + } + }, + ) + + return currentStateProvider.invoke().copy( + isEnsResultLoading = false, + addresses = combinedAddresses.toPersistentList(), + ) + } + + private fun mapAddresses( + addresses: List, + cryptoCurrency: CryptoCurrency, + ): ImmutableList { + val needUseToLegacyAndDefaultName = addresses.any { it.nameService == ReceiveAddressModel.NameService.Legacy } + + val receiveAddresses = addresses.map { model -> + val type = when (model.nameService) { + ReceiveAddressModel.NameService.Default -> { + val displayName = when (cryptoCurrency) { + is CryptoCurrency.Coin -> cryptoCurrency.name + is CryptoCurrency.Token -> cryptoCurrency.symbol + } + + Primary.Default( + displayName = if (needUseToLegacyAndDefaultName) { + TextReference.Res(R.string.domain_receive_assets_default_address) + } else { + TextReference.Combined( + wrappedList( + TextReference.Str(displayName), + TextReference.Str(" "), + TextReference.Res(R.string.common_address), + ), + ) + }, + ) + } + ReceiveAddressModel.NameService.Ens -> Ens + ReceiveAddressModel.NameService.Legacy -> Primary.Legacy( + displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address), + ) + } + ReceiveAddress( + value = model.value, + type = type, + ) + } + + val sortedAddresses = receiveAddresses.sortedWith( + compareBy { address -> + when (address.type) { + is Ens -> 0 + is Primary.Default -> 1 + is Primary.Legacy -> 2 + } + }, + ) + + return sortedAddresses.toPersistentList() + } + + private fun getNotifications( + tokenName: String, + tokenReceiveNotification: List, + ): List { + return buildList { + add( + NotificationUM.Info( + title = resourceReference( + R.string.receive_bottom_sheet_warning_title, + wrappedList( + tokenName, + cryptoCurrency.network.name, + ), + ), + subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description), + iconTint = NotificationConfig.IconTint.Accent, + ), + ) + tokenReceiveNotification.map { notification -> + add( + NotificationUM.Warning( + title = resourceReference(notification.title), + subtitle = resourceReference(notification.subtitle), + ), + ) + } + } + } +} \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveAssetsModel.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveAssetsModel.kt index 5790e3e692..e3f6ccdf6e 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveAssetsModel.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveAssetsModel.kt @@ -41,7 +41,7 @@ internal class TokenReceiveAssetsModel @Inject constructor( ReceiveAssetsUM( onCopyClick = { params.callback.onCopyClick( - id = it, + address = it, source = TokenReceiveCopyActionSource.Receive, ) }, @@ -54,7 +54,7 @@ internal class TokenReceiveAssetsModel @Inject constructor( ) private fun configureEnsStatus(): AnalyticsParam.EnsStatus { - val hasEnsAddress = params.addresses.values.any { it.type == ReceiveAddress.Type.Ens } + val hasEnsAddress = params.addresses.any { it.type == ReceiveAddress.Type.Ens } return if (hasEnsAddress) { AnalyticsParam.EnsStatus.FULL } else { diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveModel.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveModel.kt index 784431f702..a2729de835 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveModel.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveModel.kt @@ -2,22 +2,13 @@ package com.tangem.features.tokenreceive.model import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.push -import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager -import com.tangem.core.ui.R import com.tangem.core.ui.clipboard.ClipboardManager -import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.models.Asset -import com.tangem.domain.models.ReceiveAddressModel -import com.tangem.domain.models.ens.EnsAddress import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource @@ -28,12 +19,11 @@ import com.tangem.features.tokenreceive.component.TokenReceiveModelCallback import com.tangem.features.tokenreceive.entity.ReceiveAddress import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Primary +import com.tangem.features.tokenreceive.entity.TokenReceiveStateFactory import com.tangem.features.tokenreceive.route.TokenReceiveRoutes import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM +import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.collections.immutable.ImmutableMap -import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toPersistentMap import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -52,14 +42,21 @@ internal class TokenReceiveModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, ) : Model(), TokenReceiveModelCallback { - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) + private val tokenReceiveStateFactory: TokenReceiveStateFactory by lazy { + TokenReceiveStateFactory( + cryptoCurrency = params.config.cryptoCurrency, + addresses = params.config.receiveAddress, + tokenReceiveNotification = params.config.tokenReceiveNotification, + currentStateProvider = Provider { state.value }, + ) + } val params = paramsContainer.require() val stackNavigation = StackNavigation() internal val state: StateFlow - field = MutableStateFlow(getInitState()) + field = MutableStateFlow(tokenReceiveStateFactory.getInitialState(getTokenName())) init { modelScope.launch { @@ -67,20 +64,19 @@ internal class TokenReceiveModel @Inject constructor( } } - override fun onQrCodeClick(id: Int) { + override fun onQrCodeClick(address: String) { analyticsEventHandler.send( TokenReceiveNewAnalyticsEvent.QrScreenOpened( token = getTokenName(), blockchainName = params.config.cryptoCurrency.network.name, ), ) - stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(addressId = id)) + stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(address = address)) } - override fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource) { - val addressToCopy = state.value.addresses[id] ?: return - sendCopyActionAnalytic(addressToCopy, source) - clipboardManager.setText(text = addressToCopy.value, isSensitive = true) + override fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource) { + sendCopyActionAnalytic(address, source) + clipboardManager.setText(text = address.value, isSensitive = true) } override fun onShareClick(address: String) { @@ -106,51 +102,8 @@ internal class TokenReceiveModel @Inject constructor( } } - private fun mapAddresses( - addresses: List, - networkName: String, - ): ImmutableMap { - val needUseToLegacyAndDefaultName = addresses.any { it.nameService == ReceiveAddressModel.NameService.Legacy } - return buildMap { - addresses.mapIndexed { index, model -> - val type = when (model.nameService) { - ReceiveAddressModel.NameService.Default -> { - Primary.Default( - displayName = if (needUseToLegacyAndDefaultName) { - TextReference.Res(R.string.domain_receive_assets_default_address) - } else { - TextReference.Combined( - wrappedList( - TextReference.Str(networkName), - TextReference.Str(" "), - TextReference.Res(R.string.common_address), - ), - ) - }, - ) - } - ReceiveAddressModel.NameService.Ens -> Ens - ReceiveAddressModel.NameService.Legacy -> Primary.Legacy( - displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address), - ) - } - - put( - key = index, - value = ReceiveAddress( - value = model.value, - type = type, - ), - ) - } - }.toPersistentMap() - } - private suspend fun load() = withContext(dispatchers.default) { - state.value = state.value.copy( - isEnsResultLoading = true, - ) - + state.value = tokenReceiveStateFactory.getLoadingState() val reverseResolveResult = if (params.config.cryptoCurrency.network.nameResolvingType == Network.NameResolvingType.ENS) { getReverseResolvedEnsAddressUseCase( @@ -161,74 +114,7 @@ internal class TokenReceiveModel @Inject constructor( } else { emptyList() } - - val currentAddressValues = state.value.addresses.values.map { it.value }.toSet() - - val newEnsAddresses = reverseResolveResult - .filterIsInstance() - .filterNot { it.name in currentAddressValues } - .map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) } - - val combinedAddresses = (state.value.addresses.values + newEnsAddresses) - .sortedWith( - compareBy { address -> - when (address.type) { - is Ens -> 0 - is Primary.Default -> 1 - is Primary.Legacy -> 2 - } - }, - ) - - val updatedAddresses = combinedAddresses - .mapIndexed { index, address -> index to address } - .toMap() - .toPersistentMap() - - state.value = state.value.copy( - isEnsResultLoading = false, - addresses = updatedAddresses, - ) - } - - private fun getNotifications(): List { - return buildList { - add( - NotificationUM.Info( - title = resourceReference( - R.string.receive_bottom_sheet_warning_title, - wrappedList( - getTokenName(), - params.config.cryptoCurrency.network.name, - ), - ), - subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description), - iconTint = NotificationConfig.IconTint.Accent, - ), - ) - - params.config.tokenReceiveNotification.map { notification -> - add( - NotificationUM.Warning( - title = resourceReference(notification.title), - subtitle = resourceReference(notification.subtitle), - ), - ) - } - } - } - - private fun getInitState(): TokenReceiveUM { - return TokenReceiveUM( - addresses = mapAddresses( - addresses = params.config.receiveAddress, - networkName = params.config.cryptoCurrency.network.name, - ), - iconState = iconStateConverter.convert(params.config.cryptoCurrency), - network = params.config.cryptoCurrency.network.name, - isEnsResultLoading = false, - notificationConfigs = getNotifications().toImmutableList(), - ) + state.value = tokenReceiveStateFactory.getLoadedState(reverseResolveResult) } private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress, source: TokenReceiveCopyActionSource) { diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveQrCodeModel.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveQrCodeModel.kt index 92bcd28bc0..71f202d02b 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveQrCodeModel.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveQrCodeModel.kt @@ -30,7 +30,7 @@ internal class TokenReceiveQrCodeModel @Inject constructor( addressName = TextReference.Str("${params.cryptoCurrency.name} (${params.cryptoCurrency.symbol})"), onCopyClick = { params.callback.onCopyClick( - id = params.id, + address = params.address, source = TokenReceiveCopyActionSource.QR, ) }, diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/route/TokenReceiveRoutes.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/route/TokenReceiveRoutes.kt index c98a6e3c12..f4a68b87be 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/route/TokenReceiveRoutes.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/route/TokenReceiveRoutes.kt @@ -15,5 +15,5 @@ internal sealed interface TokenReceiveRoutes : Route { data object ReceiveAssets : TokenReceiveRoutes @Serializable - data class QrCode(val addressId: Int) : TokenReceiveRoutes + data class QrCode(val address: String) : TokenReceiveRoutes } \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt index 0bc0d7a0c7..48f75b359d 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt @@ -45,9 +45,7 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.tokenreceive.entity.ReceiveAddress import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.launch @Composable @@ -121,9 +119,9 @@ private fun Info( @Composable private fun AddressBlock( - onOpenQrCodeClick: (id: Int) -> Unit, - onCopyClick: (id: Int) -> Unit, - addresses: ImmutableMap, + onOpenQrCodeClick: (address: String) -> Unit, + onCopyClick: (address: ReceiveAddress) -> Unit, + addresses: ImmutableList, snackbarHostState: SnackbarHostState, ) { val hapticFeedback = LocalHapticFeedback.current @@ -131,13 +129,13 @@ private fun AddressBlock( val context = LocalContext.current val resources = context.resources - addresses.entries.toList().fastForEach { entry -> - when (val type = entry.value.type) { + addresses.fastForEach { address -> + when (val type = address.type) { is ReceiveAddress.Type.Primary -> { - key(entry.key) { + key(address.value) { AddressItem( onCopyClick = { - onCopyClick(entry.key) + onCopyClick(address) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) coroutineScope.launch { snackbarHostState.showSnackbar( @@ -149,19 +147,19 @@ private fun AddressBlock( }, onOpenQrCodeClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - onOpenQrCodeClick(entry.key) + onOpenQrCodeClick(address.value) }, - address = entry.value.value, + address = address.value, primaryType = type, ) SpacerH8() } } ReceiveAddress.Type.Ens -> { - key(entry.key) { + key(address.value) { EnsItem( onCopyClick = { - onCopyClick(entry.key) + onCopyClick(address) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) coroutineScope.launch { snackbarHostState.showSnackbar( @@ -171,7 +169,7 @@ private fun AddressBlock( ) } }, - address = entry.value.value, + address = address.value, ) SpacerH8() } @@ -334,9 +332,9 @@ private class TokenReceiveAssetsContentProvider : PreviewParameterProvider, - val onOpenQrCodeClick: (id: Int) -> Unit, - val onCopyClick: (id: Int) -> Unit, + val addresses: ImmutableList, + val onOpenQrCodeClick: (address: String) -> Unit, + val onCopyClick: (address: ReceiveAddress) -> Unit, val isEnsResultLoading: Boolean, val notificationConfigs: ImmutableList, ) \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/state/TokenReceiveUM.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/state/TokenReceiveUM.kt index 27eecde6a3..e9d6027fc2 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/state/TokenReceiveUM.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/state/TokenReceiveUM.kt @@ -4,12 +4,11 @@ import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.features.tokenreceive.entity.ReceiveAddress import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.ImmutableMap internal data class TokenReceiveUM( val network: String, val iconState: CurrencyIconState, - val addresses: ImmutableMap, + val addresses: ImmutableList, val isEnsResultLoading: Boolean, val notificationConfigs: ImmutableList, ) \ No newline at end of file From db093fec05244d5f4ede70d0b77d98f76b84897a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 14:57:14 +0400 Subject: [PATCH 14/20] Updated on 2026-08-14 --- .../data/tokens/repository/DefaultCurrenciesRepository.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 190429d257..ce7c5ddb13 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -356,10 +356,6 @@ internal class DefaultCurrenciesRepository( getMultiCurrencyWalletCurrencies(userWallet) .onEach { send(it) } .launchIn(scope = this + dispatchers.io) - - withContext(dispatchers.io) { - fetchTokensIfCacheExpired(userWallet, refresh = false) - } } } From 9100027273ee5bc085c77e04c205a7bf5fb4cdfd Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 16:56:38 +0300 Subject: [PATCH 15/20] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 05f6e8b112..5ebc59103a 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.28-1211" +tangemBlockchainSdk = "releases-5.28-1214" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.28-559" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 0afe03c67097a62a4107cdc8ede29273da41c970 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Sep 2025 16:16:27 +0200 Subject: [PATCH 16/20] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 1 + core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-uk-rUA/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 1 + .../ui/TokenReceiveAssetsContent.kt | 12 ++++----- .../ui/TokenReceiveQrCodeContent.kt | 25 ++++++++++++++----- 8 files changed, 31 insertions(+), 12 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 851e4bc48c..d0bb34dd95 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -394,6 +394,7 @@ Standardadresse Legacy adresse Empfangen von Vermögenswerten + %s Adresse Das Senden von Vermögenswerten in anderen Netzwerken führt zu dauerhaftem Verlust. %s Netzwerk Sende Geld nur mit diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index c70f5846b2..d665c40e7f 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -332,6 +332,7 @@ Dirección por defecto Dirección Legacy Recibir activos + %s dirección Enviar activos a otras redes resultará en una pérdida permanente. Envíe fondos utilizando solo Hola equipo de soporte, he encontrado un error con el código: %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 475f78e43a..228fa133e0 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -309,6 +309,7 @@ Adresse par défaut Legacy adresse Recevoir des actifs + %s adresse L’envoi d’actifs sur d’autres réseaux entraînera une perte définitive. Envoyez des fonds en utilisant uniquement Bonjour équipe de support, j’ai rencontré une erreur avec le code : %s diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 9fda11100a..a3fd7c0d13 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -308,6 +308,7 @@ Основной адрес Legacy адрес Получить активы + %s адрес Отправка средств в другой сети может повлечь потерю средств. Отправляйте средства, используя только Привет, команда поддержки, у меня возникла ошибка с кодом: %s diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 14ac369e2e..8430c62ce1 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -305,6 +305,7 @@ Основна адреса Legacy адреса Отримати активи + %s адреса Надсилання активів в інші мережі призведе до безповоротної втрати. Надсилайте кошти, використовуючи лише Привіт, команда підтримки, я зіткнувся з помилкою з кодом: %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1a6894683e..1d7c55ec0b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -395,6 +395,7 @@ Default Address Legacy Address Receive assets + %s address Sending assets in other networks will result in permanent loss. %s network Send funds using only diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt index 48f75b359d..22a746d2f2 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt @@ -29,8 +29,8 @@ import com.tangem.core.res.getStringSafe import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.SpacerW import com.tangem.core.ui.components.SpacerW12 -import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.buttons.small.TangemIconButton @@ -211,9 +211,9 @@ private fun AddressItem( Column(modifier = Modifier.weight(1f)) { EllipsisText( text = primaryType.displayName.resolveReference(), - ellipsis = TextEllipsis.Middle, + ellipsis = TextEllipsis.End, color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle1, + style = TangemTheme.typography.subtitle2, ) EllipsisText( @@ -227,16 +227,16 @@ private fun AddressItem( SpacerW12() TangemIconButton( - modifier = Modifier.size(TangemTheme.dimens.size28), + modifier = Modifier.size(TangemTheme.dimens.size32), innerPadding = 6.dp, iconRes = R.drawable.ic_qrcode_new_24, onClick = onOpenQrCodeClick, ) - SpacerW8() + SpacerW(12.dp) TangemIconButton( - modifier = Modifier.size(TangemTheme.dimens.size28), + modifier = Modifier.size(TangemTheme.dimens.size32), iconRes = R.drawable.ic_copy_new_24, innerPadding = 6.dp, onClick = onCopyClick, diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt index d248c1b34d..084e80506b 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt @@ -3,7 +3,9 @@ package com.tangem.features.tokenreceive.ui import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -87,12 +89,23 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net SpacerH(20.dp) - Image( - painter = qrCodePainter, - contentDescription = null, - contentScale = ContentScale.Fit, - modifier = Modifier.size(248.dp), - ) + Box( + modifier = Modifier + .border( + width = 8.dp, + color = TangemTheme.colors.icon.constant, + shape = RoundedCornerShape(8.dp), + ) + .padding(8.dp), + + ) { + Image( + painter = qrCodePainter, + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.sizeIn(minWidth = 148.dp), + ) + } SpacerH24() } From 0e42927a629753a2811c2d0301f671358dd7f1e8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Sep 2025 13:02:10 +0500 Subject: [PATCH 17/20] Updated on 2026-08-14 --- .../ui/amountScreen/preview/AmountStatePreviewData.kt | 6 +++--- .../com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt index fca332d04e..98f7c1e21d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt @@ -40,19 +40,19 @@ object AmountStatePreviewData { appCurrency = AppCurrency.Default, tokenName = stringReference("Tether"), amountTextField = AmountFieldModel( - value = "", + value = "2 130,81231238", onValueChange = {}, keyboardOptions = KeyboardOptions.Default, keyboardActions = KeyboardActions.Default, cryptoAmount = Amount( currencySymbol = "USDT", - value = BigDecimal.ZERO, + value = "2112312330.81212331238".toBigDecimal(), decimals = 18, type = AmountType.CoinType, ), fiatAmount = Amount( currencySymbol = "$", - value = BigDecimal.ZERO, + value = "2111232330.81".toBigDecimal(), decimals = 2, type = AmountType.CoinType, ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt index ebb5f3feba..a3cfeca883 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt @@ -121,7 +121,7 @@ private fun AmountBlockV2( ) { Column( verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(top = 8.dp), + modifier = Modifier.padding(top = 8.dp).weight(1f), ) { ResizableText( text = firstAmount, From 398c3a4fdef0e8d27c499a3800fff979999fede9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Sep 2025 13:03:27 +0500 Subject: [PATCH 18/20] Updated on 2026-08-14 --- .../send/v2/send/DefaultSendComponent.kt | 9 ++--- .../v2/sendnft/DefaultNFTSendComponent.kt | 10 +++--- .../DefaultSendWithSwapComponent.kt | 13 ++++--- .../confirm/model/SendWithSwapConfirmModel.kt | 34 ++++++++++++------- ...dWithSwapConfirmSendingStateTransformer.kt | 19 +++++++++++ ...SendWithSwapConfirmSentStateTransformer.kt | 26 ++++++++++++++ 6 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSendingStateTransformer.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSentStateTransformer.kt diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index 74c7fa09ce..c1fc30f253 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -303,12 +303,13 @@ internal class DefaultSendComponent @AssistedInject constructor( val isEmptyStack = childStack.value.backStack.isEmpty() val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success val isStubComponent = childStack.value.active.instance is StubComponent + val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isSending == true val isPopSend = isEmptyRoute || isEmptyStack || isSuccess || isStubComponent - if (isPopSend) { - router.pop() - } else { - stackNavigation.pop() + when { + isSendingInProgress -> Unit // Do not anything while transaction sending in progress + isPopSend -> router.pop() + else -> stackNavigation.pop() } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt index 2a66af2d55..f12e8292a3 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt @@ -215,11 +215,13 @@ internal class DefaultNFTSendComponent @AssistedInject constructor( val isEmptyRoute = childStack.value.active.configuration == CommonSendRoute.Empty val isEmptyStack = childStack.value.backStack.isEmpty() val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success + val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isSending == true - if (isEmptyRoute || isEmptyStack || isSuccess) { - router.pop() - } else { - stackNavigation.pop() + val isPopSend = isEmptyRoute || isEmptyStack || isSuccess + when { + isSendingInProgress -> Unit // Do not anything while transaction sending in progress + isPopSend -> router.pop() + else -> stackNavigation.pop() } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt index b59ca862d9..df40299e1b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt @@ -31,6 +31,7 @@ import com.tangem.features.swap.v2.impl.amount.SwapAmountComponent import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.features.swap.v2.impl.common.SwapUtils.SEND_WITH_SWAP_PROVIDER_TYPES +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent import com.tangem.features.swap.v2.impl.sendviaswap.model.SendWithSwapModel import com.tangem.features.swap.v2.impl.sendviaswap.success.SendWithSwapSuccessComponent @@ -202,11 +203,15 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor( private fun onChildBack() { val isEmptyStack = childStack.value.backStack.isEmpty() + val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success + val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isTransactionInProcess == true - if (isEmptyStack) { - router.pop() - } else { - stackNavigation.pop() + val isPopSend = isEmptyStack || isSuccess + + when { + isSendingInProgress -> Unit // Do not anything while transaction sending in progress + isPopSend -> router.pop() + else -> stackNavigation.pop() } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index e480cd76c5..13b3ec8c25 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -52,6 +52,8 @@ import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmInitialStateTransformer +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmSendingStateTransformer +import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmSentStateTransformer import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmationNotificationsTransformer import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM import com.tangem.lib.crypto.BlockchainFeeUtils.patchTransactionFeeForSwap @@ -241,10 +243,12 @@ internal class SendWithSwapConfirmModel @Inject constructor( private fun onSendClick() { val provider = confirmData.quote?.provider ?: return modelScope.launch { + uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(true)) swapTransactionSender.sendTransaction( confirmData = confirmData, isAmountSubtractAvailable = isAmountSubtractAvailable, onExpressError = { expressError -> + uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false)) swapAlertFactory.getGenericErrorState( expressError = expressError, onFailedTxEmailClick = { @@ -261,6 +265,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( ) }, onSendError = { error -> + uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false)) swapAlertFactory.getSendTransactionErrorState( error = error, onFailedTxEmailClick = { @@ -282,17 +287,14 @@ internal class SendWithSwapConfirmModel @Inject constructor( networkId = primaryCurrencyStatus.currency.network.id, ).getOrNull().orEmpty() sendSuccessAnalytics() - uiState.update { - it.copy( - confirmUM = ConfirmUM.Success( - isPrimaryButtonEnabled = true, - transactionDate = timestamp, - txUrl = txUrl, - provider = provider, - swapDataModel = data, - ), - ) - } + uiState.transformerUpdate( + SendWithSwapConfirmSentStateTransformer( + timestamp = timestamp, + txUrl = txUrl, + provider = provider, + swapDataModel = data, + ), + ) router.replaceAll(SendWithSwapRoute.Success) }, expressOperationType = ExpressOperationType.SEND_WITH_SWAP, @@ -428,7 +430,15 @@ internal class SendWithSwapConfirmModel @Inject constructor( backIconRes = R.drawable.ic_back_24, backIconClick = router::pop, primaryButton = NavigationButton( - textReference = resourceReference(R.string.common_send), + textReference = when (confirmUM) { + is ConfirmUM.Success -> resourceReference(R.string.common_close) + is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) { + resourceReference(R.string.send_sending) + } else { + resourceReference(R.string.common_send) + } + else -> resourceReference(R.string.common_send) + }, iconRes = R.drawable.ic_tangem_24, isIconVisible = isReadyToSend, isHapticClick = isReadyToSend, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSendingStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSendingStateTransformer.kt new file mode 100644 index 0000000000..2fc1f54df8 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSendingStateTransformer.kt @@ -0,0 +1,19 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers + +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.utils.transformer.Transformer + +internal class SendWithSwapConfirmSendingStateTransformer( + private val isTransactionInProcess: Boolean, +) : Transformer { + override fun transform(prevState: SendWithSwapUM): SendWithSwapUM { + val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState + return prevState.copy( + confirmUM = confirmUM.copy( + isPrimaryButtonEnabled = !isTransactionInProcess, + isTransactionInProcess = isTransactionInProcess, + ), + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSentStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSentStateTransformer.kt new file mode 100644 index 0000000000..970c3f1f93 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/transformers/SendWithSwapConfirmSentStateTransformer.kt @@ -0,0 +1,26 @@ +package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers + +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.swap.models.SwapDataModel +import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM +import com.tangem.utils.transformer.Transformer + +internal class SendWithSwapConfirmSentStateTransformer( + private val txUrl: String, + private val timestamp: Long, + private val provider: ExpressProvider, + private val swapDataModel: SwapDataModel, +) : Transformer { + override fun transform(prevState: SendWithSwapUM): SendWithSwapUM { + return prevState.copy( + confirmUM = ConfirmUM.Success( + isPrimaryButtonEnabled = true, + transactionDate = timestamp, + txUrl = txUrl, + provider = provider, + swapDataModel = swapDataModel, + ), + ) + } +} \ No newline at end of file From 090535656c1446bee40230e3ae61c8ee60dd2f79 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Sep 2025 11:36:20 +0200 Subject: [PATCH 19/20] Updated on 2026-08-14 --- .../features/tokenreceive/ui/TokenReceiveQrCodeContent.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt index 084e80506b..959045d098 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt @@ -75,7 +75,10 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { - Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36)) { + Column( + modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36), + horizontalAlignment = Alignment.CenterHorizontally, + ) { Text( text = stringResourceSafe( R.string.receive_bottom_sheet_warning_message_compact, From e590fffa8bc65855b9d35d004404f5509225fa07 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Sep 2025 09:45:57 +0000 Subject: [PATCH 20/20] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 5ebc59103a..72c784bae5 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.28-1214" +tangemBlockchainSdk = "develop-1216" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.28-559" +tangemCardSdk = "develop-561" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-461" +tangemHotSdk = "develop-525" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^