From e10334a9356f96ccea778b5f196eb7ea08be5be5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Mar 2026 13:58:49 +0500 Subject: [PATCH 1/4] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 4 ++ .../main/java/com/tangem/tap/MainActivity.kt | 18 ++++++++- .../ui/extensions/UserInteractionTracking.kt | 40 +++++++++++++++++++ .../data/wallets/hot/TangemHotWalletSigner.kt | 5 ++- .../usecase/SendTransactionUseCase.kt | 3 ++ .../HotAccessCodeRequestFullScreenContent.kt | 10 ++++- 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/UserInteractionTracking.kt diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 809f2808bc..3124e26244 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -9,6 +9,7 @@ import androidx.work.WorkManager import com.tangem.common.routing.AppRoute import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.wallets.hot.HotWalletPasswordRequester import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase import com.tangem.tap.LockTimerWorker.Companion.TAG import com.tangem.tap.common.extensions.dispatchNavigationAction @@ -28,6 +29,7 @@ internal class LockUserWalletsTimer( private val userWalletsListRepository: UserWalletsListRepository, private val coroutineScope: CoroutineScope, private val clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase, + private val passwordRequester: HotWalletPasswordRequester, ) : LifecycleOwner by context as LifecycleOwner, DefaultLifecycleObserver { @@ -53,6 +55,7 @@ internal class LockUserWalletsTimer( ) if (shouldOpenWelcomeScreenOnResume) { + passwordRequester.dismiss() clearAllHotWalletContextualUnlockUseCase.invoke() store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) @@ -116,6 +119,7 @@ internal class LockUserWalletsTimer( start() } .onRight { + passwordRequester.dismiss() clearAllHotWalletContextualUnlockUseCase.invoke() store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index ea612e1a2d..d6c1a7abf3 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -29,6 +30,8 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.WEBLINK_KEY import com.tangem.common.routing.deeplink.PayloadToDeeplinkConverter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.extensions.LocalUserInteractionTracker +import com.tangem.core.ui.extensions.UserInteractionTracker import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.navigation.url.UrlOpener import com.tangem.data.balancehiding.DefaultDeviceFlipDetector @@ -43,6 +46,7 @@ import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.staking.SendUnsubmittedHashesUseCase +import com.tangem.domain.wallets.hot.HotWalletPasswordRequester import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase import com.tangem.features.tester.api.TesterMenuLauncher import com.tangem.google.GoogleServicesHelper @@ -160,6 +164,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject internal lateinit var clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase + @Inject + internal lateinit var passwordRequester: HotWalletPasswordRequester + private val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -231,8 +238,16 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intent), ) + val userInteractionTracker = object : UserInteractionTracker { + override fun onUserInteraction() { + lockUserWalletsTimer?.restart() + } + } + setContent { - routingComponent.Content(Modifier.fillMaxSize()) + CompositionLocalProvider(LocalUserInteractionTracker provides userInteractionTracker) { + routingComponent.Content(Modifier.fillMaxSize()) + } } } @@ -249,6 +264,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { coroutineScope = mainScope, userWalletsListRepository = userWalletsListRepository, clearAllHotWalletContextualUnlockUseCase = clearAllHotWalletContextualUnlockUseCase, + passwordRequester = passwordRequester, ) store.dispatch( diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/UserInteractionTracking.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/UserInteractionTracking.kt new file mode 100644 index 0000000000..14adf10f6b --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/UserInteractionTracking.kt @@ -0,0 +1,40 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.input.pointer.pointerInput + +val LocalUserInteractionTracker = staticCompositionLocalOf { null } + +interface UserInteractionTracker { + fun onUserInteraction() +} + +fun Modifier.trackUserInteraction(tracker: UserInteractionTracker?): Modifier { + if (tracker == null) return this + + return this + .pointerInput(Unit) { + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + + if (down.pressed) { + tracker.onUserInteraction() + } + + waitForUpOrCancellation() + } + } + .onPreviewKeyEvent { event -> + if (event.type == KeyEventType.KeyDown) { + tracker.onUserInteraction() + } + false + } +} \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt index 58df536d84..99e2bde6ae 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt @@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.hot.HotWalletAccessor import com.tangem.hot.sdk.model.DataToSign import com.tangem.operations.sign.SignData +import com.tangem.utils.coroutines.runSuspendCatching import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -32,7 +33,7 @@ class TangemHotWalletSigner @AssistedInject constructor( TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), ) - val result = runCatching { + val result = runSuspendCatching { hotWalletAccessor.signHashes( hotWalletId = userWallet.hotWalletId, dataToSign = listOf( @@ -59,7 +60,7 @@ class TangemHotWalletSigner @AssistedInject constructor( dataToSign: List, publicKey: Wallet.PublicKey, ): CompletionResult> { - val result = runCatching { + val result = runSuspendCatching { hotWalletAccessor.signHashes( hotWalletId = userWallet.hotWalletId, dataToSign = dataToSign.map { signData -> diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index 05bc7a7445..1f273456bb 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.coroutines.cancellation.CancellationException @Suppress("LongParameterList") class SendTransactionUseCase( @@ -96,6 +97,8 @@ class SendTransactionUseCase( is Result.Success -> sendResult.data.right() } } + } catch (e: CancellationException) { + throw e } catch (ex: Exception) { cardSdkConfigRepository.setLinkedTerminal(linkedTerminal) SendTransactionError.DataError(ex.message).left() diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/ui/HotAccessCodeRequestFullScreenContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/ui/HotAccessCodeRequestFullScreenContent.kt index 691c5ef55c..bd9c1ce7d9 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/ui/HotAccessCodeRequestFullScreenContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscoderequest/ui/HotAccessCodeRequestFullScreenContent.kt @@ -18,6 +18,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.extensions.LocalUserInteractionTracker +import com.tangem.core.ui.extensions.trackUserInteraction import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.appbar.TangemTopAppBar @@ -37,7 +39,13 @@ import com.tangem.features.hotwallet.impl.R @Suppress("MagicNumber", "LongMethod") @Composable internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM, modifier: Modifier = Modifier) { - Box(Modifier.fillMaxSize()) { + val userInteractionTracker = LocalUserInteractionTracker.current + + Box( + modifier = Modifier + .fillMaxSize() + .trackUserInteraction(userInteractionTracker), + ) { AnimatedVisibility( modifier = modifier, visible = state.isShown, From a811501f80f8a9935c0c4d86e4d6ccc94bf80b73 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Mar 2026 19:34:05 +0500 Subject: [PATCH 2/4] Updated on 2026-08-14 --- .../tap/di/domain/TransactionDomainModule.kt | 6 +++ .../usecase/AssociateAssetUseCase.kt | 37 +++++++++++++------ .../usecase/OpenTrustlineUseCase.kt | 34 ++++++++++++----- .../RetryIncompleteTransactionUseCase.kt | 31 ++++++++++++---- .../tokendetails/model/TokenDetailsModel.kt | 6 +-- 5 files changed, 83 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 52ecf3a396..9cdd6d66dc 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -78,12 +78,14 @@ internal object TransactionDomainModule { walletManagersFacade: WalletManagersFacade, singleNetworkStatusSupplier: SingleNetworkStatusSupplier, multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): AssociateAssetUseCase { return AssociateAssetUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, singleNetworkStatusSupplier = singleNetworkStatusSupplier, multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + getHotTransactionSigner = tangemHotWalletSignerFactory::create, ) } @@ -92,10 +94,12 @@ internal object TransactionDomainModule { fun provideRetryTransactionUseCase( cardSdkConfigRepository: CardSdkConfigRepository, walletManagersFacade: WalletManagersFacade, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): RetryIncompleteTransactionUseCase { return RetryIncompleteTransactionUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, + getHotTransactionSigner = tangemHotWalletSignerFactory::create, ) } @@ -104,10 +108,12 @@ internal object TransactionDomainModule { fun provideOpenTrustlineUseCase( cardSdkConfigRepository: CardSdkConfigRepository, walletManagersFacade: WalletManagersFacade, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, ): OpenTrustlineUseCase { return OpenTrustlineUseCase( cardSdkConfigRepository = cardSdkConfigRepository, walletManagersFacade = walletManagersFacade, + getHotTransactionSigner = tangemHotWalletSignerFactory::create, ) } diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt index f528a1c75a..d20682fdc2 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/AssociateAssetUseCase.kt @@ -3,10 +3,12 @@ package com.tangem.domain.transaction.usecase import arrow.core.Either import arrow.core.raise.catch import arrow.core.raise.either +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkStatus +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.single.SingleNetworkStatusProducer import com.tangem.domain.networks.single.SingleNetworkStatusSupplier @@ -22,15 +24,13 @@ class AssociateAssetUseCase( private val walletManagersFacade: WalletManagersFacade, private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier, private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, + private val getHotTransactionSigner: (UserWallet.Hot) -> TransactionSigner, ) { - suspend operator fun invoke( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): Either { + suspend operator fun invoke(userWallet: UserWallet, currency: CryptoCurrency): Either { return either { val networkCoin = multiWalletCryptoCurrenciesSupplier.getSyncOrNull( - params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId), + params = MultiWalletCryptoCurrenciesProducer.Params(userWallet.walletId), ) ?.firstOrNull { val network = currency.network @@ -38,18 +38,19 @@ class AssociateAssetUseCase( } ?: error("Unable to create network coin for currencyID: ${currency.id}") - if (isBalanceZero(userWalletId, networkCoin)) { + if (isBalanceZero(userWallet.walletId, networkCoin)) { raise(AssociateAssetError.NotEnoughBalance(networkCoin)) } - val signer = cardSdkConfigRepository.getCommonSigner( - cardId = null, - twinKey = null, // use null here because no assets support for Twin cards - ) + val signer = createSigner(userWallet) catch( block = { - when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) { + when (val result = walletManagersFacade.fulfillRequirements( + userWallet.walletId, + currency, + signer, + )) { is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.customMessage)) SimpleResult.Success -> Unit } @@ -59,6 +60,20 @@ class AssociateAssetUseCase( } } + private fun createSigner(userWallet: UserWallet): TransactionSigner { + return when (userWallet) { + is UserWallet.Hot -> getHotTransactionSigner(userWallet) + is UserWallet.Cold -> getColdSigner() + } + } + + private fun getColdSigner(): TransactionSigner { + return cardSdkConfigRepository.getCommonSigner( + cardId = null, + twinKey = null, // use null here because no assets support for Twin cards + ) + } + private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean { val networkStatus = singleNetworkStatusSupplier( params = SingleNetworkStatusProducer.Params( diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/OpenTrustlineUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/OpenTrustlineUseCase.kt index 117f06541a..4f4a6c1374 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/OpenTrustlineUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/OpenTrustlineUseCase.kt @@ -4,32 +4,32 @@ import arrow.core.Either import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.error.OpenTrustlineError import com.tangem.domain.transaction.error.parseWrappedError import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.models.wallet.UserWalletId class OpenTrustlineUseCase( private val cardSdkConfigRepository: CardSdkConfigRepository, private val walletManagersFacade: WalletManagersFacade, + private val getHotTransactionSigner: (UserWallet.Hot) -> TransactionSigner, ) { - suspend operator fun invoke( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): Either { + suspend operator fun invoke(userWallet: UserWallet, currency: CryptoCurrency): Either { return either { - val signer = cardSdkConfigRepository.getCommonSigner( - cardId = null, - twinKey = null, // use null here because no assets support for Twin cards - ) + val signer = createSigner(userWallet) catch( block = { - when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) { + when (val result = walletManagersFacade.fulfillRequirements( + userWallet.walletId, + currency, + signer, + )) { is SimpleResult.Failure -> when (val error = result.error) { is BlockchainSdkError.Stellar.MinReserveRequired -> raise( @@ -58,4 +58,18 @@ class OpenTrustlineUseCase( ) } } + + private fun createSigner(userWallet: UserWallet): TransactionSigner { + return when (userWallet) { + is UserWallet.Hot -> getHotTransactionSigner(userWallet) + is UserWallet.Cold -> getColdSigner() + } + } + + private fun getColdSigner(): TransactionSigner { + return cardSdkConfigRepository.getCommonSigner( + cardId = null, + twinKey = null, // use null here because no assets support for Twin cards + ) + } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt index 09ebec6335..f4d661ee6c 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/RetryIncompleteTransactionUseCase.kt @@ -4,33 +4,36 @@ import arrow.core.Either import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.extensions.SimpleResult import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.error.IncompleteTransactionError import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.parseWrappedError import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.models.wallet.UserWalletId class RetryIncompleteTransactionUseCase( private val cardSdkConfigRepository: CardSdkConfigRepository, private val walletManagersFacade: WalletManagersFacade, + private val getHotTransactionSigner: (UserWallet.Hot) -> TransactionSigner, ) { suspend operator fun invoke( - userWalletId: UserWalletId, + userWallet: UserWallet, currency: CryptoCurrency, ): Either { return either { - val signer = cardSdkConfigRepository.getCommonSigner( - cardId = null, - twinKey = null, // use null here because no assets support for Twin cards - ) + val signer = createSigner(userWallet) catch( block = { - when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) { + when (val result = walletManagersFacade.fulfillRequirements( + userWallet.walletId, + currency, + signer, + )) { is SimpleResult.Failure -> { val error = result.error as? BlockchainSdkError when (error) { @@ -51,4 +54,18 @@ class RetryIncompleteTransactionUseCase( ) } } + + private fun createSigner(userWallet: UserWallet): TransactionSigner { + return when (userWallet) { + is UserWallet.Hot -> getHotTransactionSigner(userWallet) + is UserWallet.Cold -> getColdSigner() + } + } + + private fun getColdSigner(): TransactionSigner { + return cardSdkConfigRepository.getCommonSigner( + cardId = null, + twinKey = null, // use null here because no assets support for Twin cards + ) + } } \ No newline at end of file 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 0dc6645e21..07b45598e8 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 @@ -941,7 +941,7 @@ internal class TokenDetailsModel @Inject constructor( ) modelScope.launch { retryIncompleteTransactionUseCase( - userWalletId = userWalletId, + userWallet = userWallet, currency = cryptoCurrency, ).fold( ifLeft = { e -> @@ -982,7 +982,7 @@ internal class TokenDetailsModel @Inject constructor( ) modelScope.launch(dispatchers.mainImmediate) { openTrustlineUseCase( - userWalletId = userWalletId, + userWallet = userWallet, currency = cryptoCurrency, ).fold( ifLeft = { e -> @@ -1054,7 +1054,7 @@ internal class TokenDetailsModel @Inject constructor( ) modelScope.launch(dispatchers.io) { associateAssetUseCase( - userWalletId = userWalletId, + userWallet = userWallet, currency = cryptoCurrency, ).fold( ifLeft = { e -> From c2211d24209f193f8130e85181d6fb01697051d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 24 Mar 2026 20:47:34 +0400 Subject: [PATCH 3/4] Updated on 2026-08-14 --- .../feature/swap/DefaultSwapComponent.kt | 8 +++ .../tangem/feature/swap/model/SwapModel.kt | 62 +++++++++++++++---- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt index 033d257c1d..1ff45460be 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/DefaultSwapComponent.kt @@ -38,6 +38,7 @@ import com.tangem.utils.extensions.isZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import timber.log.Timber import java.math.BigDecimal @Suppress("UnusedPrivateMember") @@ -130,16 +131,23 @@ internal class DefaultSwapComponent @AssistedInject constructor( LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, shouldHideBlock) { if (shouldHideBlock) { + Timber.e( + "Dismissing fee selector: " + + "shouldHideBlock = $shouldHideBlock, amount = ${dataState.amount}, " + + "isInsufficientFunds = ${model.uiState.isInsufficientFunds}", + ) slotNavigation.dismiss() return@LaunchedEffect } val sendingCryptoCurrencyStatus = fromCryptoCurrency ?: run { + Timber.e("Dismissing fee selector: fromCryptoCurrency is null") slotNavigation.dismiss() return@LaunchedEffect } val feeCurrencyStatus = feePaidCryptoCurrency ?: run { + Timber.e("Dismissing fee selector: feePaidCryptoCurrency is null") slotNavigation.dismiss() return@LaunchedEffect } 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 4c90fa3f34..f0a157cbe2 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 @@ -544,7 +544,11 @@ internal class SwapModel @Inject constructor( dataState.fromCryptoCurrency } - if (fromCryptoCurrency != null) updateFeePaidCryptoCurrencyFor(fromCryptoCurrency) + if (fromCryptoCurrency != null) { + updateFeePaidCryptoCurrencyFor(fromCryptoCurrency) + } else { + Timber.e("updateFeePaidCryptoCurrencyFor failed: fromCryptoCurrency is null") + } subscribeToCoinBalanceUpdatesIfNeeded() }.onFailure { error -> @@ -771,7 +775,14 @@ internal class SwapModel @Inject constructor( feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase( userWalletId = userWalletId, cryptoCurrencyStatus = fromToken, - ).getOrNull(), + ) + .onLeft { Timber.e("Unable to get fee paid crypto currency status for ${fromToken.currency.id}") } + .onRight { currencyStatus -> + if (currencyStatus == null) { + Timber.e("Fee paid crypto currency status is null for ${fromToken.currency.id}") + } + } + .getOrNull(), ) } @@ -1046,6 +1057,7 @@ internal class SwapModel @Inject constructor( val fee = getSelectedFee() if (fee == null && tangemPayInput?.isWithdrawal != true) { + Timber.e("onSwapClick: fee is null and isWithdrawal is ${tangemPayInput?.isWithdrawal}") showAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) modelScope.launch { delay(SWAP_IN_PROGRESS_DELAY) @@ -1072,6 +1084,7 @@ internal class SwapModel @Inject constructor( when (swapTransactionState) { is SwapTransactionState.TxSent -> { if (fee == null) { + Timber.e("onSwapClick: onSuccess: fee is null after swap") showAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) return@onSuccess } @@ -1408,16 +1421,16 @@ internal class SwapModel @Inject constructor( } modelScope.launch { updateFeePaidCryptoCurrencyFor(fromToken) + startLoadingQuotes( + fromToken = fromToken, + fromAccount = fromAccount, + toToken = toToken, + toAccount = toAccount, + amount = lastAmount.value, + reduceBalanceBy = lastReducedBalanceBy.value, + toProvidersList = findSwapProviders(fromToken, toToken), + ) } - startLoadingQuotes( - fromToken = fromToken, - fromAccount = fromAccount, - toToken = toToken, - toAccount = toAccount, - amount = lastAmount.value, - reduceBalanceBy = lastReducedBalanceBy.value, - toProvidersList = findSwapProviders(fromToken, toToken), - ) updateTokensState(tokens) } } @@ -2314,10 +2327,14 @@ internal class SwapModel @Inject constructor( private fun getSelectedFeeState(): TxFeeSealedState { val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content - ?: return TxFeeSealedState.Legacy( + + if (feeStateUM == null) { + Timber.e("getSelectedFeeState: FeeSelectorUM is not Content: $feeStateUM, returning Legacy state") + return TxFeeSealedState.Legacy( txFeeState = TxFeeState.Empty, selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL, ) + } val transactionFeeExtended = feeStateUM.feeExtraInfo.transactionFeeExtended return TxFeeSealedState.Component( @@ -2331,7 +2348,13 @@ internal class SwapModel @Inject constructor( } private fun getSelectedFee(): TxFee? { - val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content ?: return null + val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content + + if (feeStateUM == null) { + Timber.e("getSelectedFee: FeeSelectorUM is not Content: $feeStateUM, returning null") + return null + } + val transactionFeeExtended = feeStateUM.feeExtraInfo.transactionFeeExtended return TxFee.FeeComponent( @@ -2387,6 +2410,7 @@ internal class SwapModel @Inject constructor( if (newState is FeeSelectorUM.Error) { modelScope.launch { + Timber.e("onResult: FeeSelectorUM is Error, isHidden = true") forceUpdateState.emit(newState.copy(isHidden = true)) } return @@ -2401,6 +2425,8 @@ internal class SwapModel @Inject constructor( newState.feeExtraInfo.feeCryptoCurrencyStatus.currency is CryptoCurrency.Coin if (isFeeCurrencySameAsFromCurrency || isCoinFeeSelected) { + Timber.e("onResult: Fee currency is same as from currency or coin fee selected, reloading quotes") + // block swap button until fee is loaded uiState = uiState.copy( swapButton = uiState.swapButton.copy( @@ -2423,15 +2449,19 @@ internal class SwapModel @Inject constructor( } override suspend fun loadFee(): Either { + Timber.e("loadFee: Start loading fee") + val fromToken = dataState.fromCryptoCurrency ?: return Either.Left(GetFeeError.UnknownError) val toToken = dataState.toCryptoCurrency ?: return Either.Left(GetFeeError.UnknownError) val selectedProvider = dataStateStateFlow.first { it.selectedProvider != null }.selectedProvider!! if (dataState.lastLoadedSwapStates[selectedProvider] !is SwapState.QuotesLoadedState) { + Timber.e("loadFee: Quotes not loaded ${dataState.lastLoadedSwapStates[selectedProvider]}") return Either.Left(GetFeeError.UnknownError) } if (isPermissionNotificationShown()) { + Timber.e("loadFee: Permission notification is shown, cannot load fee") return Either.Left(GetFeeError.UnknownError) } @@ -2444,6 +2474,12 @@ internal class SwapModel @Inject constructor( amount = lastAmount.value, reduceBalanceBy = lastReducedBalanceBy.value, ) + .onLeft { + Timber.e("loadFee: Failed to load fee with error $it") + } + .onRight { + Timber.e("loadFee: Fee loaded successfully") + } } override fun choosingInProgress(updatedState: Boolean) { From 6ce3abf924fb8a4d3fb653cef4c824137a298094 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Mar 2026 10:35:24 +0000 Subject: [PATCH 4/4] 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 f98056e6bf..5f02e77766 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.35-1449" +tangemBlockchainSdk = "releases-5.36-1457" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.35-596" +tangemCardSdk = "releases-5.36-599" #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-547" +tangemHotSdk = "develop-549" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^