From a2d3ac38c9a3836ca0f1b35678267258512ad4ed Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Dec 2024 16:34:32 +0500 Subject: [PATCH 01/40] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 9 ++++- .../tap/common/url/CustomTabsUrlOpener.kt | 37 ++++++++++++++++++- .../feature/swap/router/CustomTabsManager.kt | 1 + .../router/DefaultTokenDetailsRouter.kt | 9 ++++- .../router/DefaultWalletRouter.kt | 9 ++++- 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b9db373358..8981d0a0b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,13 @@ + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt index 7fe3ef0081..965d6caacd 100644 --- a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt @@ -1,8 +1,11 @@ package com.tangem.tap.common.url import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT @@ -12,6 +15,7 @@ import com.tangem.tap.common.extensions.getColorCompat import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.withForegroundActivity import com.tangem.wallet.R +import timber.log.Timber internal class CustomTabsUrlOpener : UrlOpener { @@ -34,6 +38,37 @@ internal class CustomTabsUrlOpener : UrlOpener { ) .build() - customTabsIntent.launchUrl(context, Uri.parse(url)) + customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + runCatching { + if (checkCustomTabsAvailability(context, browserIntent)) { + context.startActivity(browserIntent) + } else { + customTabsIntent.launchUrl(context, Uri.parse(url)) + } + }.onFailure { + Timber.e(it.message) + } + } + + /** + * Custom Tabs compatibility check. Returns flag whether custom tabs are supported + * @see "https://developer.chrome.com/docs/android/custom-tabs/howto-custom-tab-check" + */ + private fun checkCustomTabsAvailability(context: Context, browserIntent: Intent): Boolean { + // Get all apps that can handle VIEW intents and Custom Tab service connections. + val resolveInfos = context.packageManager.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL) + + // Extract package names from ResolveInfo objects + val packageNames = mutableListOf() + for (info in resolveInfos) { + packageNames.add(info.activityInfo.packageName) + } + + // Get a package that supports Custom Tabs + val packageName = CustomTabsClient.getPackageName(context, packageNames, true) + + return packageName == null // Custom Tabs are not supported by any browser on the device } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt index 565d27a3b8..af04294b72 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt @@ -8,6 +8,7 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import java.lang.ref.WeakReference +@Deprecated("Replace with CustomTabsUrlOpener") class CustomTabsManager(private val context: WeakReference) { fun openUrl(url: String) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt index 52eae5de76..d8a3eb4733 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt @@ -50,6 +50,13 @@ internal class DefaultTokenDetailsRouter( } override fun openOnrampSuccess(externalTxId: String) { - router.push(AppRoute.OnrampSuccess(externalTxId)) + // finish current onramp flow and show onramp success screen + val replaceOnrampScreens = router.stack + .filterNot { it is AppRoute.Onramp } + .toMutableList() + + replaceOnrampScreens.add(AppRoute.OnrampSuccess(externalTxId)) + + router.replaceAll(*replaceOnrampScreens.toTypedArray()) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 4d2df2bd9f..6780b00e47 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -130,7 +130,14 @@ internal class DefaultWalletRouter( } override fun openOnrampSuccessScreen(externalTxId: String) { - router.push(AppRoute.OnrampSuccess(externalTxId)) + // finish current onramp flow and show onramp success screen + val replaceOnrampScreens = router.stack + .filterNot { it is AppRoute.Onramp } + .toMutableList() + + replaceOnrampScreens.add(AppRoute.OnrampSuccess(externalTxId)) + + router.replaceAll(*replaceOnrampScreens.toTypedArray()) } override fun openUrl(url: String) { From bf2c65eca37f1dd64d61870c1654d689c54ae8b7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Dec 2024 20:14:07 +0500 Subject: [PATCH 02/40] Updated on 2026-08-14 --- .../tangem/core/deeplink/global/BuyCurrencyDeepLink.kt | 9 ++------- .../com/tangem/data/onramp/DefaultOnrampRepository.kt | 2 +- .../presentation/tokendetails/TokenDetailsPreviewData.kt | 2 ++ .../presentation/tokendetails/state/TokenDetailsState.kt | 1 + .../state/factory/TokenDetailsSkeletonStateConverter.kt | 1 + .../state/factory/express/ExpressStatusFactory.kt | 7 +++++++ .../state/factory/express/OnrampStatusFactory.kt | 2 +- .../presentation/tokendetails/ui/TokenDetailsScreen.kt | 2 +- .../tokendetails/viewmodels/TokenDetailsViewModel.kt | 4 ++-- .../presentation/deeplink/WalletDeepLinksHandler.kt | 1 - 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt index b9427aee10..259ffd2cbb 100644 --- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt @@ -3,15 +3,10 @@ package com.tangem.core.deeplink.global import com.tangem.core.deeplink.DeepLink class BuyCurrencyDeepLink( - isOnrampFeatureEnabled: Boolean, val onReceive: (externalTxId: String) -> Unit, ) : DeepLink { - override val uri: String = if (isOnrampFeatureEnabled) { - ONRAMP_REDIRECT_DEEPLINK - } else { - BUY_REDIRECT_DEEPLINK - } + override val uri = BUY_REDIRECT_DEEPLINK override fun onReceive(params: Map) { onReceive( @@ -20,7 +15,7 @@ class BuyCurrencyDeepLink( } companion object { - const val ONRAMP_REDIRECT_DEEPLINK = "tangem://onramp-success?" + const val ONRAMP_REDIRECT_DEEPLINK = "https://tangem.com/success?action=dismissBrowser" private const val BUY_REDIRECT_DEEPLINK = "tangem://redirect?action=dismissBrowser" } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index ca78dbeca6..1ada8bde19 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -415,7 +415,7 @@ internal class DefaultOnrampRepository( fromCurrency = currency, toAmount = quote.toAmount.value, toCurrencyId = cryptoCurrency.id.value, - status = OnrampStatus.Status.Expired, + status = OnrampStatus.Status.Created, externalTxUrl = onrampDataJson.externalTxUrl, externalTxId = onrampDataJson.externalTxId, timestamp = DateTime.now().millis, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 625959ab86..358d295129 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -308,6 +308,7 @@ internal object TokenDetailsPreviewData { dialogConfig = null, pendingTxs = persistentListOf(), expressTxs = persistentListOf(), + expressTxsToDisplay = persistentListOf(), pullToRefreshConfig = pullToRefreshConfig, bottomSheetConfig = null, isBalanceHidden = false, @@ -338,6 +339,7 @@ internal object TokenDetailsPreviewData { dialogConfig = null, pendingTxs = persistentListOf(), expressTxs = persistentListOf(), + expressTxsToDisplay = persistentListOf(), pullToRefreshConfig = pullToRefreshConfig, bottomSheetConfig = null, isBalanceHidden = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 8ece0d02e9..d285c89fc9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -21,6 +21,7 @@ internal data class TokenDetailsState( val stakingBlocksState: StakingBlockUM?, val notifications: ImmutableList, val pendingTxs: PersistentList, + val expressTxsToDisplay: PersistentList, val expressTxs: PersistentList, val txHistoryState: TxHistoryState, val dialogConfig: TokenDetailsDialogConfig?, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 07f98c6f10..f22431609c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -67,6 +67,7 @@ internal class TokenDetailsSkeletonStateConverter( notifications = persistentListOf(), pendingTxs = persistentListOf(), expressTxs = persistentListOf(), + expressTxsToDisplay = persistentListOf(), txHistoryState = TxHistoryState.Content( contentItems = MutableStateFlow( value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 92a6a2508c..aa5d0ecc92 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -98,8 +98,15 @@ internal class ExpressStatusFactory @AssistedInject constructor( if (currentTx is ExpressTransactionStateUM.ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) { updateBalance(currentTx.toCryptoCurrency) } + val expressTxsToDisplay = expressTxs.filterNot { + when (it) { + is ExpressTransactionStateUM.ExchangeUM -> false + is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden + } + }.toPersistentList() return state.copy( expressTxs = expressTxs, + expressTxsToDisplay = expressTxsToDisplay, bottomSheetConfig = currentTx?.let( ::updateStateWithExpressStatusBottomSheet, ) ?: config, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index eca24c7c13..3e2cbe8813 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -61,7 +61,7 @@ internal class OnrampStatusFactory @AssistedInject constructor( ifRight = { onrampTxs -> val transactions = onrampTransactionStateConverter.convertList(onrampTxs) transactions.clearHiddenTerminal() - transactions.filterNot { it.activeStatus.isHidden } + transactions }, ifLeft = { persistentListOf() }, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index a22b97d642..a522838fe3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -178,7 +178,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon } expressTransactionsItems( - expressTxs = state.expressTxs, + expressTxs = state.expressTxsToDisplay, modifier = itemModifier, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 480de2e7f2..55d9d5ecb8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -198,7 +198,6 @@ internal class TokenDetailsViewModel @Inject constructor( viewModel = this, deepLinks = listOf( BuyCurrencyDeepLink( - isOnrampFeatureEnabled = onrampFeatureToggles.isFeatureEnabled, onReceive = ::onBuyCurrencyDeepLink, ), ), @@ -336,6 +335,7 @@ internal class TokenDetailsViewModel @Inject constructor( expressTxStatusTaskScheduler.scheduleTask( viewModelScope, PeriodicTask( + isDelayFirst = false, delay = EXPRESS_STATUS_UPDATE_DELAY, task = { runCatching { @@ -805,7 +805,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onExpressTransactionClick(txId: String) { - val expressTxState = internalUiState.value.expressTxs.first { it.info.txId == txId } + val expressTxState = internalUiState.value.expressTxsToDisplay.first { it.info.txId == txId } internalUiState.value = expressStatusFactory.getStateWithExpressStatusBottomSheet(expressTxState) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index c9abd72ef6..d729196c4f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -65,7 +65,6 @@ internal class WalletDeepLinksHandler @Inject constructor( if (onrampFeatureToggles.isFeatureEnabled || !userWallet.isMultiCurrency) { add( BuyCurrencyDeepLink( - isOnrampFeatureEnabled = onrampFeatureToggles.isFeatureEnabled, onReceive = { externalTxId -> scope.launch { onBuyCurrencyDeepLink(externalTxId, userWallet) } }, From cf53970ba8a90452f1da10d2000145451b8fdb16 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 11:10:17 +0500 Subject: [PATCH 03/40] Updated on 2026-08-14 --- .../response/model/OnrampCurrencyDTO.kt | 2 +- .../data/onramp/DefaultOnrampRepository.kt | 7 ++++-- .../domain/onramp/model/OnrampCurrency.kt | 2 +- .../onramp/model/OnrampProviderWithQuote.kt | 2 +- .../tangem/domain/onramp/model/OnrampQuote.kt | 7 +++++- .../GetOnrampProviderWithQuoteUseCase.kt | 23 +++++++++++++------ .../domain/onramp/GetOnrampQuotesUseCase.kt | 4 ++-- .../onramp/main/entity/AmountBlockState.kt | 7 +++++- .../amount/OnrampAmountStateFactory.kt | 9 ++++---- .../main/model/OnrampMainComponentModel.kt | 16 +++++++------ .../providers/entity/SelectProviderResult.kt | 2 +- .../entity/OnrampSuccessComponentUM.kt | 2 +- 12 files changed, 54 insertions(+), 29 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt index 7dbb49bbdf..a2a62175ec 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt @@ -12,7 +12,7 @@ data class OnrampCurrencyDTO( val code: String, @Json(name = "image") - val image: String, + val image: String?, @Json(name = "precision") val precision: Int, diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 1ada8bde19..f3e0ead995 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -465,7 +465,7 @@ internal class DefaultOnrampRepository( ) = if (error is ApiResponseError.HttpException) { val onrampError = onrampErrorConverter.convert(value = error.errorBody.orEmpty()) if (onrampError is OnrampError.AmountError) { - OnrampQuote.Error( + OnrampQuote.AmountError( paymentMethod = paymentMethod, provider = provider, fromAmount = fromOnrampAmount, @@ -473,7 +473,10 @@ internal class DefaultOnrampRepository( ) } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") - null + OnrampQuote.Error( + paymentMethod = paymentMethod, + provider = provider, + ) } } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt index 20f225f582..8b5bdbf34b 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable data class OnrampCurrency( val name: String, val code: String, - val image: String, + val image: String?, val precision: Int, val unit: String, ) \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt index f0d51802d6..2891d6072d 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt @@ -23,7 +23,7 @@ sealed interface OnrampProviderWithQuote { data class Error( override val provider: OnrampProvider, - val quoteError: OnrampQuote.Error, + val quoteError: OnrampQuote.AmountError, ) : Unavailable } } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index cc6a4586c1..033d3f3f87 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -16,10 +16,15 @@ sealed class OnrampQuote { val maxFromAmount: OnrampAmount, ) : OnrampQuote() - data class Error( + data class AmountError( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, val fromAmount: OnrampAmount, val error: OnrampError.AmountError, ) : OnrampQuote() + + data class Error( + override val paymentMethod: OnrampPaymentMethod, + override val provider: OnrampProvider, + ) : OnrampQuote() } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt index 22238ab748..1d6c6700d6 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt @@ -31,8 +31,8 @@ class GetOnrampProviderWithQuoteUseCase( private fun List.quoteWithProvider( provider: OnrampProvider, selectedPaymentMethod: OnrampPaymentMethod, - ): OnrampProviderWithQuote { - val matchedQuote = firstOrNull { it.paymentMethod == selectedPaymentMethod } + ): OnrampProviderWithQuote? { + val matchedQuote = firstOrNull { it.paymentMethod.id == selectedPaymentMethod.id } return when (matchedQuote) { is OnrampQuote.Data -> OnrampProviderWithQuote.Data( provider = matchedQuote.provider, @@ -40,14 +40,23 @@ class GetOnrampProviderWithQuoteUseCase( toAmount = matchedQuote.toAmount, fromAmount = matchedQuote.fromAmount, ) - is OnrampQuote.Error -> Unavailable.Error( + is OnrampQuote.AmountError -> Unavailable.Error( provider = matchedQuote.provider, quoteError = matchedQuote, ) - null -> Unavailable.NotSupportedPaymentMethod( - provider = provider, - availablePaymentMethods = provider.paymentMethods, - ) + is OnrampQuote.Error -> null + null -> { + val availablePaymentMethods = getAvailablePaymentMethods(provider) + Unavailable.NotSupportedPaymentMethod( + provider = provider, + availablePaymentMethods = availablePaymentMethods, + ).takeIf { availablePaymentMethods.isNotEmpty() } + } } } + + private fun List.getAvailablePaymentMethods(provider: OnrampProvider) = provider.paymentMethods + .filter { pm -> + filter { it !is OnrampQuote.Error }.any { it.paymentMethod.id == pm.id } + } } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt index 1da7243f67..24b27d4860 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt @@ -47,9 +47,9 @@ class GetOnrampQuotesUseCase( grouped.value.sortedByDescending { when (it) { is OnrampQuote.Data -> it.toAmount.value - + is OnrampQuote.Error -> null // negative difference to sort both when data and unavailable is present - is OnrampQuote.Error -> { + is OnrampQuote.AmountError -> { when (val error = it.error) { is OnrampError.AmountError.TooSmallError -> it.fromAmount.value - error.requiredAmount is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.fromAmount.value diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt index d0cfbe8515..c56ecedb67 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt @@ -10,7 +10,12 @@ internal data class OnrampAmountBlockUM( val secondaryFieldModel: OnrampAmountSecondaryFieldUM, ) -internal data class OnrampCurrencyUM(val code: String, val iconUrl: String, val precision: Int, val onClick: () -> Unit) +internal data class OnrampCurrencyUM( + val code: String, + val iconUrl: String?, + val precision: Int, + val onClick: () -> Unit, +) @Immutable internal sealed interface OnrampAmountSecondaryFieldUM { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 5d85cb0561..70d7f41345 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -75,7 +75,7 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy( - secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState), + secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel, ), providerBlockState = quote.toProviderBlockState(isBestRate), buyButtonConfig = currentState.buyButtonConfig.copy( @@ -175,19 +175,20 @@ internal class OnrampAmountStateFactory( ) } - private fun OnrampQuote.toSecondaryFieldUiModel(amountState: OnrampAmountBlockUM): OnrampAmountSecondaryFieldUM { + private fun OnrampQuote.toSecondaryFieldUiModel(amountState: OnrampAmountBlockUM): OnrampAmountSecondaryFieldUM? { return when (this) { + is OnrampQuote.Error -> null is OnrampQuote.Data -> { val amount = toAmount.value.format { crypto(symbol = toAmount.symbol, decimals = toAmount.decimals) } OnrampAmountSecondaryFieldUM.Content(stringReference(amount)) } - is OnrampQuote.Error -> this.toSecondaryFieldUiModel(amountState) + is OnrampQuote.AmountError -> this.toSecondaryFieldUiModel(amountState) } } - private fun OnrampQuote.Error.toSecondaryFieldUiModel( + private fun OnrampQuote.AmountError.toSecondaryFieldUiModel( amountState: OnrampAmountBlockUM, ): OnrampAmountSecondaryFieldUM.Error { val amount = error.requiredAmount.format { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 366e4ba313..a7955b8c84 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -196,6 +196,8 @@ internal class OnrampMainComponentModel @Inject constructor( private fun subscribeToQuotesUpdate() { getOnrampQuotesUseCase.invoke() + .distinctUntilChanged() + .conflate() .onEach { maybeQuotes -> maybeQuotes.fold( ifLeft = ::handleOnrampError, @@ -269,7 +271,7 @@ internal class OnrampMainComponentModel @Inject constructor( } private fun handleQuoteResult(quotes: List) { - quotes.filterIsInstance().forEach { errorState -> + quotes.filterIsInstance().forEach { errorState -> sendOnrampErrorAnalytic(errorState.error) } @@ -299,12 +301,12 @@ internal class OnrampMainComponentModel @Inject constructor( paymentMethod = quote.paymentMethod.name, ), ) - } - _state.update { - amountStateFactory.getAmountSecondaryUpdatedState( - quote = quote, - isBestRate = isBestProvider, - ) + _state.update { + amountStateFactory.getAmountSecondaryUpdatedState( + quote = quote, + isBestRate = isBestProvider, + ) + } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt index 434138e8ab..3f1364105d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt @@ -20,6 +20,6 @@ sealed class SelectProviderResult { data class ProviderWithError( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, - val quoteError: OnrampQuote.Error, + val quoteError: OnrampQuote.AmountError, ) : SelectProviderResult() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt index 71f80e2f05..cb5a0cd776 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt @@ -11,7 +11,7 @@ sealed class OnrampSuccessComponentUM { data class Content( val txId: String, val timestamp: Long, - val currencyImageUrl: String, + val currencyImageUrl: String?, val fromAmount: TextReference, val toAmount: TextReference, val statusBlock: ExpressStatusUM, From ed480ea1753cfe938931e0b256d83e83b99281d4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 12:05:52 +0500 Subject: [PATCH 04/40] Updated on 2026-08-14 --- features/markets/impl/build.gradle.kts | 1 + .../impl/model/TokenActionsHandler.kt | 12 ++++++++---- .../viewmodels/TokenDetailsViewModel.kt | 18 +++++++++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 4f73123e32..87db7e37d6 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -14,6 +14,7 @@ android { dependencies { /* Project - API */ api(projects.features.markets.api) + api(projects.features.onramp.api) implementation(projects.core.navigation) /* Domain */ diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 34923a5f2d..ce9e2b7841 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -23,6 +23,7 @@ import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.impl.loader.PortfolioData import com.tangem.features.markets.portfolio.impl.ui.WarningDialog import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM +import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.utils.Provider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -41,12 +42,15 @@ internal class TokenActionsHandler @AssistedInject constructor( @Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit, private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, + private val onrampFeatureToggles: OnrampFeatureToggles, ) { - private val disabledActionsInDemoMode = setOf( - TokenActionsBSContentUM.Action.Buy, - TokenActionsBSContentUM.Action.Sell, - ) + private val disabledActionsInDemoMode = buildSet { + if (!onrampFeatureToggles.isFeatureEnabled) { + add(TokenActionsBSContentUM.Action.Buy) + } + add(TokenActionsBSContentUM.Action.Sell) + } fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { onHandleQuickAction( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 55d9d5ecb8..6690f0a085 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -462,9 +462,8 @@ internal class TokenDetailsViewModel @Inject constructor( return } - showErrorIfDemoModeOrElse { - val status = cryptoCurrencyStatus ?: return@showErrorIfDemoModeOrElse - + val status = cryptoCurrencyStatus ?: return + if (onrampFeatureToggles.isFeatureEnabled) { viewModelScope.launch(dispatchers.main) { reduxStateHolder.dispatch( TradeCryptoAction.Buy( @@ -475,6 +474,19 @@ internal class TokenDetailsViewModel @Inject constructor( ), ) } + } else { + showErrorIfDemoModeOrElse { + viewModelScope.launch(dispatchers.main) { + reduxStateHolder.dispatch( + TradeCryptoAction.Buy( + userWallet = userWallet, + source = OnrampSource.TOKEN_DETAILS, + cryptoCurrencyStatus = status, + appCurrencyCode = selectedAppCurrencyFlow.value.code, + ), + ) + } + } } } From 2c4158b057396f7ddc84c4084da43329ad15e7e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 14:01:06 +0500 Subject: [PATCH 05/40] Updated on 2026-08-14 --- .../data/onramp/DefaultOnrampRepository.kt | 20 ++++++------------- .../onramp/GetOnrampRedirectUrlUseCase.kt | 2 ++ .../onramp/repositories/OnrampRepository.kt | 1 + .../DefaultOnrampRedirectComponent.kt | 7 +++++++ .../redirect/model/OnrampRedirectModel.kt | 7 ++----- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index f3e0ead995..3d7221ff2d 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -32,10 +32,8 @@ import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObject -import com.tangem.datasource.local.preferences.utils.getObjectSyncOrDefault import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.storeObject -import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.onramp.model.* import com.tangem.domain.onramp.model.cache.OnrampTransaction import com.tangem.domain.onramp.model.error.OnrampError @@ -281,6 +279,7 @@ internal class DefaultOnrampRepository( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, quote: OnrampProviderWithQuote.Data, + isDarkTheme: Boolean, ): OnrampTransaction = withContext(dispatchers.io) { try { val address = requireNotNull( @@ -306,7 +305,7 @@ internal class DefaultOnrampRepository( toAddress = address, redirectUrl = BuyCurrencyDeepLink.ONRAMP_REDIRECT_DEEPLINK, language = null, - theme = getTheme(), + theme = getTheme(isDarkTheme), requestId = requestId, ).bind() }, @@ -442,17 +441,10 @@ internal class DefaultOnrampRepository( ) } - private suspend fun getTheme(): String { - val appTheme = appPreferencesStore.getObjectSyncOrDefault( - key = PreferencesKeys.APP_THEME_MODE_KEY, - default = AppThemeMode.DEFAULT, - ) - return when (appTheme) { - AppThemeMode.FORCE_DARK -> PROVIDER_THEME_DARK - AppThemeMode.FORCE_LIGHT, - AppThemeMode.FOLLOW_SYSTEM, - -> PROVIDER_THEME_LIGHT - } + private fun getTheme(isDarkTheme: Boolean): String = if (isDarkTheme) { + PROVIDER_THEME_DARK + } else { + PROVIDER_THEME_LIGHT } private fun List.removeApplePay(): List = filterNot { it.id == "apple-pay" } diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt index 34bb9f5202..55ba74a5bf 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt @@ -19,12 +19,14 @@ class GetOnrampRedirectUrlUseCase( userWalletId: UserWalletId, quote: OnrampProviderWithQuote.Data, cryptoCurrency: CryptoCurrency, + isDarkTheme: Boolean, ): Either { return Either.catch { val transaction = repository.getOnrampData( userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, quote = quote, + isDarkTheme = isDarkTheme, ) transactionRepository.storeTransaction(transaction) transaction.redirectUrl diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 87e9bff955..302a563b12 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -24,6 +24,7 @@ interface OnrampRepository { userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, quote: OnrampProviderWithQuote.Data, + isDarkTheme: Boolean, ): OnrampTransaction // cache diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt index 71cedbb3a5..17904c58e6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt @@ -1,7 +1,9 @@ package com.tangem.features.onramp.redirect import androidx.activity.compose.BackHandler +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel @@ -22,6 +24,11 @@ internal class DefaultOnrampRedirectComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { BackHandler(onBack = params.onBack) OnrampRedirectContent(modifier = modifier, state = model.state) + + val isDarkTheme = isSystemInDarkTheme() + LaunchedEffect(model.state) { + model.getRedirectUrl(isDarkTheme = isDarkTheme) + } } @AssistedFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt index f15be660c5..0ce73d2372 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt @@ -62,16 +62,13 @@ internal class OnrampRedirectModel @Inject constructor( ), ) - init { - getRedirectUrl() - } - - private fun getRedirectUrl() { + fun getRedirectUrl(isDarkTheme: Boolean) { modelScope.launch { getOnrampRedirectUrlUseCase.invoke( userWalletId = params.userWalletId, quote = params.onrampProviderWithQuote, cryptoCurrency = params.cryptoCurrency, + isDarkTheme = isDarkTheme, ) .onLeft(::handleError) .onRight { From f6151916ad5e50802af4f4d73e61cefb9ea77966 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 14:17:03 +0400 Subject: [PATCH 06/40] Updated on 2026-08-14 --- .../repository/DefaultCurrenciesRepository.kt | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 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 ea747277bc..4fb9ef47d2 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 @@ -138,32 +138,32 @@ internal class DefaultCurrenciesRepository( } private fun populateCurrenciesWithMissedCoins(currencies: List): List { - val networkToCoin = mutableMapOf() - val networkToTokens = mutableMapOf>() + val currenciesSequence = currencies.asSequence() - currencies.forEach { currency -> - when (currency) { - is CryptoCurrency.Coin -> { - networkToCoin[currency.network] = currency - } - is CryptoCurrency.Token -> { - val tokens = networkToTokens.getOrElse(currency.network, ::emptyList) - networkToTokens[currency.network] = tokens + currency - } - } - } + val networksWithTokens = currenciesSequence + .filterIsInstance() + .map { it.network } + .distinct() + + val networksWithCoins = currenciesSequence + .filterIsInstance() + .map { it.network } + .distinct() + + val networksNeedingCoins = (networksWithTokens - networksWithCoins.toSet()).toMutableList() + + if (networksNeedingCoins.isEmpty()) return currencies return buildList { - if (networkToTokens.isEmpty()) { - // add only coin here - addAll(networkToCoin.values) - } else { - networkToTokens.forEach { (network, networkTokens) -> - val networkCoin = networkToCoin[network] ?: cryptoCurrencyFactory.createCoin(network) + currencies.forEach { currency -> + if (currency is CryptoCurrency.Token && currency.network in networksNeedingCoins) { + val coin = cryptoCurrencyFactory.createCoin(currency.network) + add(coin) - add(networkCoin) - addAll(networkTokens) + networksNeedingCoins.remove(currency.network) } + + add(currency) } } } From fb5c125110fb54956f40aacdd04b807cb0e6a661 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 17:10:14 +0500 Subject: [PATCH 07/40] Updated on 2026-08-14 --- .../data/onramp/DefaultOnrampRepository.kt | 1 + .../tangem/domain/onramp/model/OnrampQuote.kt | 1 + .../onramp/analytics/OnrampAnalyticsEvent.kt | 9 +++--- .../amount/OnrampAmountStateFactory.kt | 2 +- .../main/model/OnrampMainComponentModel.kt | 32 ++++++++++++++++--- .../providers/model/SelectProviderModel.kt | 1 + .../redirect/model/OnrampRedirectModel.kt | 1 + .../model/OnrampSuccessComponentModel.kt | 10 +++++- .../utils/OnrampErrorAnalyticsSender.kt | 2 ++ 9 files changed, 49 insertions(+), 10 deletions(-) diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 3d7221ff2d..85dbdb56f6 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -468,6 +468,7 @@ internal class DefaultOnrampRepository( OnrampQuote.Error( paymentMethod = paymentMethod, provider = provider, + error = onrampError, ) } } else { diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index 033d3f3f87..c511e04b8a 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -26,5 +26,6 @@ sealed class OnrampQuote { data class Error( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, + val error: OnrampError, ) : OnrampQuote() } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt index e577385a98..cf54baee5b 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt @@ -1,7 +1,6 @@ package com.tangem.domain.onramp.analytics import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.core.analytics.models.AnalyticsParam.Key.CURRENCY import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_DESCRIPTION import com.tangem.core.analytics.models.AnalyticsParam.Key.PAYMENT_METHOD @@ -37,7 +36,7 @@ sealed class OnrampAnalyticsEvent( private val currency: String, ) : OnrampAnalyticsEvent( event = "Currency Chosen", - params = mapOf(CURRENCY to currency), + params = mapOf("Currency Type" to currency), ) data object CloseOnramp : OnrampAnalyticsEvent(event = "Button - Close") @@ -114,7 +113,7 @@ sealed class OnrampAnalyticsEvent( event = "Button - Buy", params = mapOf( PROVIDER to providerName, - CURRENCY to currency, + "Currency Type" to currency, TOKEN_PARAM to tokenSymbol, ), ) @@ -131,7 +130,7 @@ sealed class OnrampAnalyticsEvent( TOKEN_PARAM to tokenSymbol, PROVIDER to providerName, RESIDENCE to residence, - CURRENCY to currency, + "Currency Type" to currency, PAYMENT_METHOD to paymentMethod, ), ) @@ -143,11 +142,13 @@ sealed class OnrampAnalyticsEvent( private val tokenSymbol: String, private val errorCode: String, private val providerName: String?, + private val paymentMethod: String?, ) : OnrampAnalyticsEvent( event = "Errors", params = buildMap { put(TOKEN_PARAM, tokenSymbol) providerName?.let { put(PROVIDER, providerName) } + paymentMethod?.let { put(PAYMENT_METHOD, paymentMethod) } put(ERROR_CODE, errorCode) }, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 70d7f41345..93b66cbc95 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -204,7 +204,7 @@ internal class OnrampAmountStateFactory( R.string.onramp_max_amount_restriction } is OnrampError.AmountError.TooSmallError -> { - analyticsEventHandler.send(OnrampAnalyticsEvent.MaxAmountError) + analyticsEventHandler.send(OnrampAnalyticsEvent.MinAmountError) R.string.onramp_min_amount_restriction } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index a7955b8c84..ddcbb59d1c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -81,7 +81,7 @@ internal class OnrampMainComponentModel @Inject constructor( private val _state: MutableStateFlow = MutableStateFlow( value = stateFactory.getInitialState( currency = params.cryptoCurrency.name, - onClose = router::pop, + onClose = ::onCloseClick, ), ) private val quotesTaskScheduler = SingleTaskScheduler() @@ -270,10 +270,13 @@ internal class OnrampMainComponentModel @Inject constructor( super.onDestroy() } + private fun onCloseClick() { + analyticsEventHandler.send(OnrampAnalyticsEvent.CloseOnramp) + router.pop() + } + private fun handleQuoteResult(quotes: List) { - quotes.filterIsInstance().forEach { errorState -> - sendOnrampErrorAnalytic(errorState.error) - } + sendOnrampQuotesErrorAnalytic(quotes) val quote = quotes.firstOrNull() @@ -346,9 +349,30 @@ internal class OnrampMainComponentModel @Inject constructor( error = error, tokenSymbol = params.cryptoCurrency.symbol, providerName = providerContent?.providerName, + paymentMethod = providerContent?.paymentMethod?.name, ) } + private fun sendOnrampQuotesErrorAnalytic(quotes: List) { + quotes.forEach { errorState -> + when (errorState) { + is OnrampQuote.Error -> analyticsEventHandler.sendOnrampErrorEvent( + error = errorState.error, + tokenSymbol = params.cryptoCurrency.symbol, + providerName = errorState.provider.info.name, + paymentMethod = errorState.paymentMethod.name, + ) + is OnrampQuote.AmountError -> analyticsEventHandler.sendOnrampErrorEvent( + error = errorState.error, + tokenSymbol = params.cryptoCurrency.symbol, + providerName = errorState.provider.info.name, + paymentMethod = errorState.paymentMethod.name, + ) + else -> { /* no-op */ } + } + } + } + private companion object { const val UPDATE_DELAY = 10_000L } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index e4b6bdb60b..1c94f39113 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -259,6 +259,7 @@ internal class SelectProviderModel @Inject constructor( error = error, tokenSymbol = params.cryptoCurrency.symbol, providerName = selectedProvider?.name, + paymentMethod = state.value.selectedPaymentMethod.paymentMethod.name, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt index 0ce73d2372..91b94beb8e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt @@ -84,6 +84,7 @@ internal class OnrampRedirectModel @Inject constructor( error = error, tokenSymbol = params.cryptoCurrency.symbol, providerName = params.onrampProviderWithQuote.provider.info.name, + paymentMethod = params.onrampProviderWithQuote.paymentMethod.name, ) val contentMessage = ContentMessage { onDismiss -> BasicDialog( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt index 348cbe0154..935b18a42f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -81,6 +81,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( error = error, tokenSymbol = cryptoCurrency.symbol, providerName = transaction.providerName, + paymentMethod = transaction.paymentMethod, ) Timber.e(error.toString()) }, @@ -101,7 +102,12 @@ internal class OnrampSuccessComponentModel @Inject constructor( goToProviderClick = ::goToProviderClick, ).convert(status) } - removeTransactionIfTerminalStatus(cryptoCurrency, transaction.providerName, status) + removeTransactionIfTerminalStatus( + cryptoCurrency = cryptoCurrency, + providerName = transaction.providerName, + paymentMethod = transaction.paymentMethod, + status = status, + ) }, ) } @@ -109,6 +115,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( private fun removeTransactionIfTerminalStatus( cryptoCurrency: CryptoCurrency, providerName: String, + paymentMethod: String, status: OnrampStatus, ) { modelScope.launch { @@ -118,6 +125,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( error = error, tokenSymbol = cryptoCurrency.symbol, providerName = providerName, + paymentMethod = paymentMethod, ) } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt index 5ca1b735b6..e9ec5c76c5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt @@ -8,11 +8,13 @@ internal fun AnalyticsEventHandler.sendOnrampErrorEvent( error: OnrampError, tokenSymbol: String, providerName: String? = null, + paymentMethod: String? = null, ) = when (error) { is OnrampError.DataError -> send( OnrampAnalyticsEvent.Errors( tokenSymbol = tokenSymbol, providerName = providerName, + paymentMethod = paymentMethod, errorCode = error.code, ), ) From e3578607446ad8d2b88f9870902cfbe65c8d95c8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 18:34:29 +0400 Subject: [PATCH 08/40] Updated on 2026-08-14 --- .../src/main/assets/configs/excluded_blockchains_config.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json index 8d520b49a3..5a48cc92c0 100644 --- a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json +++ b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json @@ -7,6 +7,10 @@ "name": "NEXA", "version": "undefined" }, + { + "name": "NEXA/test", + "version": "undefined" + }, { "name": "clore-ai", "version": "undefined" From e4e0dbdea132cd3a559594c356846d24addfbede Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 18:34:41 +0400 Subject: [PATCH 09/40] Updated on 2026-08-14 --- .../impl/DefaultExcludedBlockchainsManager.kt | 52 ++++++++++--------- .../local/preferences/PreferencesKeys.kt | 2 +- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt index 8c208bae34..0a260230e9 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DefaultExcludedBlockchainsManager.kt @@ -6,8 +6,8 @@ import com.tangem.core.configtoggle.utils.associateToggles import com.tangem.core.configtoggle.version.VersionProvider import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getSyncOrDefault -import com.tangem.datasource.local.preferences.utils.store +import com.tangem.datasource.local.preferences.utils.getObjectMapSync +import com.tangem.datasource.local.preferences.utils.storeObjectMap internal class DefaultExcludedBlockchainsManager( private val localTogglesStorage: TogglesStorage, @@ -17,62 +17,64 @@ internal class DefaultExcludedBlockchainsManager( private var isInitialized: Boolean = false - private lateinit var currentExcludedBlockchainsIds: Set - private lateinit var localExcludedBlockchainsIds: Set + private lateinit var currentExcludedBlockchains: MutableMap + private lateinit var localExcludedBlockchains: Map override val excludedBlockchainsIds: Set get() { if (!isInitialized) error("ExcludedBlockchainsManager is not initialized") - return currentExcludedBlockchainsIds + return currentExcludedBlockchains + .filterValues { it } + .keys } override suspend fun init() { localTogglesStorage.populate(path = "configs/excluded_blockchains_config") - val storedExcludedBlockchainsIds = appPreferencesStore.getSyncOrDefault( + val storedExcludedBlockchainsIds = appPreferencesStore.getObjectMapSync( key = PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY, - default = emptySet(), ) - localExcludedBlockchainsIds = localTogglesStorage.toggles + localExcludedBlockchains = localTogglesStorage.toggles .associateToggles(currentVersion = versionProvider.get().orEmpty()) - .filterValues { isIncluded -> !isIncluded } - .keys + .mapValues { (_, isIncluded) -> !isIncluded } - if (storedExcludedBlockchainsIds.isEmpty()) { - recoverLocalConfig() - } else { - currentExcludedBlockchainsIds = storedExcludedBlockchainsIds - } + currentExcludedBlockchains = (localExcludedBlockchains.keys + storedExcludedBlockchainsIds.keys) + .fold(mutableMapOf()) { acc, blockchainId -> + val isExcluded = storedExcludedBlockchainsIds[blockchainId] ?: localExcludedBlockchains[blockchainId] + + requireNotNull(isExcluded) { + "Unable to find $blockchainId in local or stored excluded blockchains" + } + + acc[blockchainId] = isExcluded + acc + } isInitialized = true } override suspend fun excludeBlockchain(mainnetId: String, isExcluded: Boolean) { - if (isExcluded) { - currentExcludedBlockchainsIds += mainnetId - } else { - currentExcludedBlockchainsIds -= mainnetId - } + currentExcludedBlockchains[mainnetId] = isExcluded storeCurrent() } override fun isMatchLocalConfig(): Boolean { - return currentExcludedBlockchainsIds == localExcludedBlockchainsIds + return currentExcludedBlockchains == localExcludedBlockchains } override suspend fun recoverLocalConfig() { - currentExcludedBlockchainsIds = localExcludedBlockchainsIds + currentExcludedBlockchains = localExcludedBlockchains.toMutableMap() storeCurrent() } private suspend fun storeCurrent() { - appPreferencesStore.store( - PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY, - currentExcludedBlockchainsIds, + appPreferencesStore.storeObjectMap( + key = PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY, + value = currentExcludedBlockchains, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index df2f985c7d..0e9a1885e9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -55,7 +55,7 @@ object PreferencesKeys { val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") } - val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringSetPreferencesKey(name = "excludedBlockchains") } + val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringPreferencesKey(name = "excludedBlockchainsV2") } val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") } From b15d95bc40c147dbb855d0ba4d33483355d03982 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 18:21:42 +0300 Subject: [PATCH 10/40] Updated on 2026-08-14 --- app/src/main/res/layout/fragment_onboarding_wallet.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index c9fb79355a..98ad59717a 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -178,9 +178,9 @@ android:id="@+id/tv_body" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/home_bottom_tv_body_margin_start" + android:layout_marginStart="32dp" android:layout_marginTop="@dimen/home_bottom_tv_body_margin_top" - android:layout_marginEnd="@dimen/home_bottom_tv_body_margin_end" + android:layout_marginEnd="32dp" android:textAlignment="center" android:textColor="@color/text_secondary" android:textSize="16sp" From 5bef8947726848a2c080d6510818743b508a832a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Dec 2024 19:18:19 +0300 Subject: [PATCH 11/40] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cc51c50b34..85cc0139a0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.19-883" +tangemBlockchainSdk = "release-app_5.19-885" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.19-414" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From cf8009da88c31102eb54810c193195ebb5aa48e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 11:07:32 +0300 Subject: [PATCH 12/40] Updated on 2026-08-14 --- .../wallet/presentation/wallet/domain/Wallet2CobrandImage.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt index 033f180725..80663dd7de 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt @@ -161,7 +161,7 @@ internal enum class Wallet2CobrandImage( Pastel( cards2ResId = R.drawable.ill_pastel_cards3_120_106, cards3ResId = R.drawable.ill_pastel_cards3_120_106, - batchIds = setOf("AF43", "AF44", "AF45"), + batchIds = setOf("AF43", "AF44", "AF45", "AF78", "AF79", "AF80"), ), RedPanda( @@ -204,7 +204,7 @@ internal enum class Wallet2CobrandImage( Vivid( cards2ResId = R.drawable.ill_vivid_cards3_120_106, cards3ResId = R.drawable.ill_vivid_cards3_120_106, - batchIds = setOf("AF40", "AF41", "AF42"), + batchIds = setOf("AF40", "AF41", "AF42", "AF75", "AF76", "AF77"), ), VoltInu( From d2d93773863b8f5d19c58042ce9f8834e336a493 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 13:16:08 +0300 Subject: [PATCH 13/40] Updated on 2026-08-14 --- .../tokendetails/TokenDetailsPreviewData.kt | 1 + .../presentation/tokendetails/state/StakingBlockUM.kt | 1 + .../factory/TokenDetailsLoadedBalanceConverter.kt | 10 +++++++++- .../ui/components/staking/TokenStakingBlock.kt | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 358d295129..672da4f66c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -152,6 +152,7 @@ internal object TokenDetailsPreviewData { formatArgs = wrappedList("Solana"), ), iconState = iconState, + isEnabled = true, onStakeClicked = {}, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/StakingBlockUM.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/StakingBlockUM.kt index 0072e90707..789ddc5ec5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/StakingBlockUM.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/StakingBlockUM.kt @@ -24,6 +24,7 @@ internal sealed interface StakingBlockUM { val iconState: IconState, val titleText: TextReference, val subtitleText: TextReference, + val isEnabled: Boolean, val onStakeClicked: () -> Unit, ) : StakingBlockUM } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index a8bf99a969..2a9be57347 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -158,7 +158,7 @@ internal class TokenDetailsLoadedBalanceConverter( return when { stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> { if (pendingBalances.isEmpty()) { - getStakeAvailableState(stakingEntryInfo, iconState) + getStakeAvailableState(stakingEntryInfo, iconState, isStakingButtonEnabled(status)) } else { getStakedBlockWithFiatAmount(status, pendingBalances.sumOf { it.amount }, null) } @@ -170,6 +170,12 @@ internal class TokenDetailsLoadedBalanceConverter( } } + private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean { + return status.value is CryptoCurrencyStatus.Loaded || + status.value is CryptoCurrencyStatus.NoQuote || + status.value is CryptoCurrencyStatus.Custom + } + private fun getStakedBlockWithFiatAmount( status: CryptoCurrencyStatus, stakingAmount: BigDecimal?, @@ -210,6 +216,7 @@ internal class TokenDetailsLoadedBalanceConverter( private fun getStakeAvailableState( stakingEntryInfo: StakingEntryInfo, iconState: IconState, + isEnabled: Boolean, ): StakingBlockUM.StakeAvailable { val apr = stakingEntryInfo.apr.format { percent() } return StakingBlockUM.StakeAvailable( @@ -222,6 +229,7 @@ internal class TokenDetailsLoadedBalanceConverter( formatArgs = wrappedList(stakingEntryInfo.tokenSymbol), ), iconState = iconState, + isEnabled = isEnabled, onStakeClicked = clickIntents::onStakeBannerClick, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt index a8ba9f84c2..484de66cba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt @@ -112,6 +112,7 @@ private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifi SecondaryButton( modifier = Modifier.fillMaxWidth(), text = stringResource(id = R.string.common_stake), + enabled = state.isEnabled, onClick = state.onStakeClicked, ) } From d28ebee0f0cb51ab0ea054888dd44c061f843dc1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 13:16:57 +0300 Subject: [PATCH 14/40] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 92629c43f6..f6655e8627 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -5,7 +5,7 @@ Продать Это может занять несколько секунд. Пожалуйста, попробуйте позже. Данные ещё не загрузились - Это действие в данный момент недоступно, пожалуйста попробуйте позже или обновите данные, сделав свайп экрана вниз. + Это действие в данный момент недоступно, пожалуйста, попробуйте позже или обновите данные, сделав свайп экрана вниз. Кнопка недоступна Выберите токен Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для обмена. From 7bc1381aa9e1fbd547d5d590d9c102c2bc2601a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 17:25:13 +0300 Subject: [PATCH 15/40] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt | 8 +++++--- .../com/tangem/feature/swap/viewmodels/SwapViewModel.kt | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 18f3cff503..33d7f81b88 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -30,7 +30,7 @@ internal class LockUserWalletsTimer( lifecycle.addObserver(this) } - override fun onResume(owner: LifecycleOwner) { + override fun onStart(owner: LifecycleOwner) { owner.lifecycleScope.launch { val wasApplicationStopped = settingsRepository.wasApplicationStopped() val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume() @@ -45,8 +45,6 @@ internal class LockUserWalletsTimer( settingsRepository.setWasApplicationStopped(value = false) - start() - if (shouldOpenWelcomeScreenOnResume) { store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false) @@ -54,6 +52,10 @@ internal class LockUserWalletsTimer( } } + override fun onResume(owner: LifecycleOwner) { + start() + } + override fun onStop(owner: LifecycleOwner) { Timber.i("Owner stopped") diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 431017f917..13de36639d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -185,6 +185,14 @@ internal class SwapViewModel @Inject constructor( }.launchIn(viewModelScope) } + override fun onStart(owner: LifecycleOwner) { + startLoadingQuotesFromLastState(true) + } + + override fun onStop(owner: LifecycleOwner) { + singleTaskScheduler.cancelTask() + } + override fun onCleared() { singleTaskScheduler.cancelTask() super.onCleared() From 1cc16506e1a61c8c070ea35c63bbeca3c40aa11e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 22:57:32 +0500 Subject: [PATCH 16/40] Updated on 2026-08-14 --- .../data/onramp/DefaultOnrampRepository.kt | 1 + .../tangem/domain/onramp/model/OnrampQuote.kt | 6 +- .../providers/entity/ProviderListItemUM.kt | 48 ++++++---- .../onramp/providers/entity/ProviderListUM.kt | 7 +- .../providers/model/SelectProviderModel.kt | 87 +++++++++++++------ .../previewData/SelectProviderPreviewData.kt | 72 ++++++++++----- .../providers/ui/SelectProviderBottomSheet.kt | 19 +++- 7 files changed, 162 insertions(+), 78 deletions(-) diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 85dbdb56f6..25a92979ef 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -468,6 +468,7 @@ internal class DefaultOnrampRepository( OnrampQuote.Error( paymentMethod = paymentMethod, provider = provider, + fromAmount = fromOnrampAmount, error = onrampError, ) } diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index c511e04b8a..c36638db32 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -6,11 +6,12 @@ sealed class OnrampQuote { abstract val paymentMethod: OnrampPaymentMethod abstract val provider: OnrampProvider + abstract val fromAmount: OnrampAmount data class Data( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, - val fromAmount: OnrampAmount, + override val fromAmount: OnrampAmount, val toAmount: OnrampAmount, val minFromAmount: OnrampAmount, val maxFromAmount: OnrampAmount, @@ -19,13 +20,14 @@ sealed class OnrampQuote { data class AmountError( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, - val fromAmount: OnrampAmount, + override val fromAmount: OnrampAmount, val error: OnrampError.AmountError, ) : OnrampQuote() data class Error( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, + override val fromAmount: OnrampAmount, val error: OnrampError, ) : OnrampQuote() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt index 0e6727be5b..f3c9d5341b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt @@ -6,30 +6,42 @@ internal sealed interface ProviderListItemUM { val providerId: String val name: String + val imageUrl: String - data class Available( - override val providerId: String, - override val name: String, - val imageUrl: String, - val rate: String, - val isBestRate: Boolean, - val diffRate: TextReference?, - val isSelected: Boolean, - val onClick: () -> Unit, - ) : ProviderListItemUM + sealed interface Available : ProviderListItemUM { + val onClick: () -> Unit + val providerResult: SelectProviderResult + val isBestRate: Boolean + val isSelected: Boolean - data class AvailableWithError( - override val providerId: String, - override val name: String, - val imageUrl: String, - val subtitle: TextReference, - val onClick: () -> Unit, - ) : ProviderListItemUM + data class Content( + override val providerId: String, + override val name: String, + override val onClick: () -> Unit, + override val imageUrl: String, + override val providerResult: SelectProviderResult, + override val isBestRate: Boolean, + override val isSelected: Boolean, + val rate: String, + val diffRate: TextReference?, + ) : Available + + data class WithError( + override val providerId: String, + override val name: String, + override val onClick: () -> Unit, + override val imageUrl: String, + override val providerResult: SelectProviderResult, + override val isBestRate: Boolean, + override val isSelected: Boolean, + val subtitle: TextReference, + ) : Available + } data class Unavailable( override val providerId: String, override val name: String, - val imageUrl: String, + override val imageUrl: String, val subtitle: TextReference, ) : ProviderListItemUM } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt index 28350458da..b4f1435144 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListUM.kt @@ -4,13 +4,14 @@ import com.tangem.domain.onramp.model.OnrampPaymentMethod import kotlinx.collections.immutable.ImmutableList internal data class SelectPaymentAndProviderUM( - val paymentMethods: ImmutableList, - val selectedPaymentMethod: SelectProviderUM, + val paymentMethods: ImmutableList, + val selectedPaymentMethod: PaymentProviderUM, + val selectedProviderId: String, val isPaymentMethodClickEnabled: Boolean, val onPaymentMethodClick: () -> Unit, ) -internal data class SelectProviderUM( +internal data class PaymentProviderUM( val paymentMethod: OnrampPaymentMethod, val providers: ImmutableList, ) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index 1c94f39113..e85759d361 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -72,9 +72,21 @@ internal class SelectProviderModel @Inject constructor( }, ifRight = { it }, ) + + val filteredEmptyMethods = methods.mapNotNull { method -> + val providers = getOnrampProviderWithQuoteUseCase(method).getOrNull() + if (!providers.isNullOrEmpty()) { + PaymentProviderUM( + paymentMethod = method, + providers = providers.toProvidersListItems(), + ) + } else { + null + } + } _state.value = state.value.copy( - paymentMethods = methods.toPersistentList(), - isPaymentMethodClickEnabled = methods.isNotEmpty(), + paymentMethods = filteredEmptyMethods.toPersistentList(), + isPaymentMethodClickEnabled = filteredEmptyMethods.isNotEmpty(), ) } } @@ -111,13 +123,19 @@ internal class SelectProviderModel @Inject constructor( private fun getInitialState(): SelectPaymentAndProviderUM { return SelectPaymentAndProviderUM( - paymentMethods = persistentListOf(params.selectedPaymentMethod), + paymentMethods = persistentListOf( + PaymentProviderUM( + paymentMethod = params.selectedPaymentMethod, + providers = emptyList().toImmutableList(), + ), + ), isPaymentMethodClickEnabled = false, onPaymentMethodClick = ::openPaymentMethods, - selectedPaymentMethod = SelectProviderUM( + selectedPaymentMethod = PaymentProviderUM( paymentMethod = params.selectedPaymentMethod, providers = emptyList().toImmutableList(), ), + selectedProviderId = params.selectedProviderId, ) } @@ -131,16 +149,18 @@ internal class SelectProviderModel @Inject constructor( ) } - private fun ImmutableList.toPaymentUMList(): ImmutableList = map { method -> + private fun ImmutableList.toPaymentUMList(): ImmutableList = map { method -> PaymentMethodUM( - id = method.id, - imageUrl = method.imageUrl, - name = method.name, + id = method.paymentMethod.id, + imageUrl = method.paymentMethod.imageUrl, + name = method.paymentMethod.name, onSelect = { onPaymentMethodSelected(method) }, ) }.toPersistentList() - private fun onPaymentMethodSelected(paymentMethod: OnrampPaymentMethod) { + private fun onPaymentMethodSelected(methodContainer: PaymentProviderUM) { + val paymentMethod = methodContainer.paymentMethod + val firstProvider = methodContainer.providers.firstOrNull() analyticsEventHandler.send(OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = paymentMethod.name)) modelScope.launch { saveSelectedPaymentMethod.invoke(paymentMethod) @@ -151,6 +171,10 @@ internal class SelectProviderModel @Inject constructor( ), ) } + if (firstProvider is ProviderListItemUM.Available) { + onProviderSelected(firstProvider.providerResult, firstProvider.isBestRate) + } + bottomSheetNavigation.dismiss() } } @@ -165,6 +189,8 @@ internal class SelectProviderModel @Inject constructor( val hasBestProvider = isMultipleQuotes && isOtherQuotesHasData return sorted.map { quote -> + val isSelectedProvider = state.value.selectedProviderId == quote.provider.id + when (quote) { is OnrampProviderWithQuote.Data -> { val rate = quote.toAmount.value.format { @@ -173,28 +199,29 @@ internal class SelectProviderModel @Inject constructor( val rateDiff = bestProvider?.toAmount?.value?.let { bestRate -> BigDecimal.ONE - quote.toAmount.value / bestRate } - val isSelectedProvider = quote.provider.id == params.selectedProviderId - val isSelectedPayment = quote.paymentMethod.id == params.selectedPaymentMethod.id val isBestProvider = quote == bestProvider && hasBestProvider - ProviderListItemUM.Available( + val providerResult = SelectProviderResult.ProviderWithQuote( + paymentMethod = quote.paymentMethod, + provider = quote.provider, + fromAmount = quote.fromAmount, + toAmount = quote.toAmount, + ) + ProviderListItemUM.Available.Content( providerId = quote.provider.id, imageUrl = quote.provider.info.imageLarge, name = quote.provider.info.name, rate = rate, - isSelected = isSelectedProvider && isSelectedPayment, isBestRate = isBestProvider, diffRate = stringReference("$MINUS${rateDiff.format { percent() }}") .takeIf { hasBestProvider }, + providerResult = providerResult, + isSelected = isSelectedProvider, onClick = { onProviderSelected( - result = SelectProviderResult.ProviderWithQuote( - paymentMethod = quote.paymentMethod, - provider = quote.provider, - fromAmount = quote.fromAmount, - toAmount = quote.toAmount, - ), + result = providerResult, isBestRate = isBestProvider, ) + params.onDismiss() }, ) } @@ -207,21 +234,25 @@ internal class SelectProviderModel @Inject constructor( is OnrampError.AmountError.TooBigError -> R.string.express_provider_max_amount is OnrampError.AmountError.TooSmallError -> R.string.express_provider_min_amount } - - ProviderListItemUM.AvailableWithError( + val providerResult = SelectProviderResult.ProviderWithError( + paymentMethod = quoteError.paymentMethod, + provider = quote.provider, + quoteError = quote.quoteError, + ) + ProviderListItemUM.Available.WithError( providerId = quote.provider.id, imageUrl = quote.provider.info.imageLarge, name = quote.provider.info.name, subtitle = resourceReference(errorSubtitleRes, wrappedList(amount)), + providerResult = providerResult, + isBestRate = false, + isSelected = isSelectedProvider, onClick = { onProviderSelected( - result = SelectProviderResult.ProviderWithError( - paymentMethod = quoteError.paymentMethod, - provider = quote.provider, - quoteError = quote.quoteError, - ), + result = providerResult, isBestRate = bestProvider == quote, ) + params.onDismiss() }, ) } @@ -247,8 +278,10 @@ internal class SelectProviderModel @Inject constructor( tokenSymbol = params.cryptoCurrency.symbol, ), ) + _state.update { + it.copy(selectedProviderId = result.provider.id) + } params.onProviderClick(result, isBestRate) - params.onDismiss() } private fun sendOnrampErrorEvent(error: OnrampError) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt index 4110250555..f4a23e8734 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt @@ -2,59 +2,83 @@ package com.tangem.features.onramp.providers.model.previewData import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.onramp.model.OnrampPaymentMethod -import com.tangem.domain.onramp.model.PaymentMethodType +import com.tangem.domain.onramp.model.* +import com.tangem.features.onramp.providers.entity.PaymentProviderUM import com.tangem.features.onramp.providers.entity.ProviderListItemUM import com.tangem.features.onramp.providers.entity.SelectPaymentAndProviderUM -import com.tangem.features.onramp.providers.entity.SelectProviderUM +import com.tangem.features.onramp.providers.entity.SelectProviderResult import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal internal object SelectProviderPreviewData { - private val paymentMethods = persistentListOf( - OnrampPaymentMethod( - id = "card", - name = "Card", - imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png", - type = PaymentMethodType.CARD, - ), + private val paymentMethod = OnrampPaymentMethod( + id = "card", + name = "Card", + imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png", + type = PaymentMethodType.CARD, ) - private val providerListUM = SelectProviderUM( - paymentMethod = paymentMethods.first(), + private val provider = OnrampProvider( + id = "mercuryo", + info = OnrampProviderInfo( + name = "Mercuryo", + imageLarge = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", + termsOfUseLink = null, + privacyPolicyLink = null, + ), + paymentMethods = listOf(), + ) + + private val providerListUM = PaymentProviderUM( + paymentMethod = paymentMethod, providers = persistentListOf( - ProviderListItemUM.Available( - providerId = "mercuryo", - imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", - name = "Mercuryo", + ProviderListItemUM.Available.Content( + providerId = provider.id, + imageUrl = provider.info.imageLarge, + name = provider.info.name, rate = "1 BTC", isSelected = true, isBestRate = true, diffRate = TextReference.EMPTY, + providerResult = SelectProviderResult.ProviderWithQuote( + paymentMethod = paymentMethod, + provider = provider, + fromAmount = OnrampAmount(symbol = "$", value = BigDecimal.ONE, decimals = 2), + toAmount = OnrampAmount(symbol = "BTC", value = BigDecimal.ONE, decimals = 18), + + ), onClick = {}, ), - ProviderListItemUM.Available( - providerId = "mercuryo", - imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", - name = "Mercuryo", + ProviderListItemUM.Available.Content( + providerId = provider.id, + imageUrl = provider.info.imageLarge, + name = provider.info.name, rate = "1 BTC", isSelected = false, isBestRate = false, diffRate = stringReference("-0.03%"), + providerResult = SelectProviderResult.ProviderWithQuote( + paymentMethod = paymentMethod, + provider = provider, + fromAmount = OnrampAmount(symbol = "$", value = BigDecimal.ONE, decimals = 2), + toAmount = OnrampAmount(symbol = "BTC", value = BigDecimal.ONE, decimals = 18), + ), onClick = {}, ), ProviderListItemUM.Unavailable( - providerId = "mercuryo", - imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", - name = "Mercuryo", + providerId = provider.id, + imageUrl = provider.info.imageLarge, + name = provider.info.name, subtitle = stringReference("Avaiable from 0,00413 BTC"), ), ), ) val state = SelectPaymentAndProviderUM( - paymentMethods = paymentMethods, + paymentMethods = persistentListOf(providerListUM), selectedPaymentMethod = providerListUM, + selectedProviderId = provider.id, isPaymentMethodClickEnabled = false, onPaymentMethodClick = {}, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt index 9918d1f05e..071e09b4b6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt @@ -124,16 +124,17 @@ private fun PaymentMethodBlock( @Composable private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifier) { when (state) { - is ProviderListItemUM.Available -> AvailableProviderItem( + is ProviderListItemUM.Available.Content -> AvailableProviderItem( modifier = modifier, state = state, ) - is ProviderListItemUM.AvailableWithError -> { + is ProviderListItemUM.Available.WithError -> { UnavailableProviderItem( modifier = modifier.clickable(onClick = state.onClick), imageUrl = state.imageUrl, providerName = state.name, subtitle = state.subtitle, + isSelected = state.isSelected, ) } is ProviderListItemUM.Unavailable -> UnavailableProviderItem( @@ -141,12 +142,13 @@ private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifie imageUrl = state.imageUrl, providerName = state.name, subtitle = state.subtitle, + isSelected = false, ) } } @Composable -private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: Modifier = Modifier) { +private fun AvailableProviderItem(state: ProviderListItemUM.Available.Content, modifier: Modifier = Modifier) { Row( modifier = modifier .then( @@ -220,10 +222,19 @@ private fun UnavailableProviderItem( imageUrl: String, providerName: String, subtitle: TextReference, + isSelected: Boolean, modifier: Modifier = Modifier, ) { Row( - modifier = modifier.padding(all = TangemTheme.dimens.spacing12), + modifier = modifier + .then( + if (isSelected) { + Modifier.selectedBorder() + } else { + Modifier.clip(RoundedCornerShape(16.dp)) + }, + ) + .padding(all = TangemTheme.dimens.spacing12), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { From 7fcabe68fa9d717e74a523d6941bf79c965fde14 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 22:56:10 +0500 Subject: [PATCH 17/40] Updated on 2026-08-14 --- .../main/model/OnrampMainComponentModel.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index ddcbb59d1c..ef27622623 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -278,10 +278,11 @@ internal class OnrampMainComponentModel @Inject constructor( private fun handleQuoteResult(quotes: List) { sendOnrampQuotesErrorAnalytic(quotes) - val quote = quotes.firstOrNull() + val quote = quotes.firstOrNull { it !is OnrampQuote.Error } if (quote == null) { _state.update { stateFactory.getErrorState() } + lastAmount.value = BigDecimal.ZERO return } @@ -295,15 +296,17 @@ internal class OnrampMainComponentModel @Inject constructor( val isBestProvider = quote == bestProvider && hasBestProvider - if (quote is OnrampQuote.Data && lastAmount.value != quote.fromAmount.value) { + if (lastAmount.value != quote.fromAmount.value) { lastAmount.value = quote.fromAmount.value - analyticsEventHandler.send( - OnrampAnalyticsEvent.ProviderCalculated( - providerName = quote.provider.info.name, - tokenSymbol = params.cryptoCurrency.symbol, - paymentMethod = quote.paymentMethod.name, - ), - ) + if (quote is OnrampQuote.Data) { + analyticsEventHandler.send( + OnrampAnalyticsEvent.ProviderCalculated( + providerName = quote.provider.info.name, + tokenSymbol = params.cryptoCurrency.symbol, + paymentMethod = quote.paymentMethod.name, + ), + ) + } _state.update { amountStateFactory.getAmountSecondaryUpdatedState( quote = quote, From 71a0fd55673a0396261e8f1d4c859fac97ebdaba Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 22:56:29 +0500 Subject: [PATCH 18/40] Updated on 2026-08-14 --- .../confirmresidency/ConfirmResidencyComponent.kt | 2 +- .../DefaultConfirmResidencyComponent.kt | 10 ++++++++-- .../entity/ConfirmResidencyBottomSheetConfig.kt | 2 +- .../model/ConfirmResidencyModel.kt | 12 +++++++----- .../onramp/main/DefaultOnrampMainComponent.kt | 2 +- .../onramp/main/model/OnrampMainComponentModel.kt | 14 +++++++++----- 6 files changed, 27 insertions(+), 15 deletions(-) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index feb6cd9c68..7dda243f37 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -10,7 +10,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { data class Params( val cryptoCurrency: CryptoCurrency, val country: OnrampCountry, - val onDismiss: (OnrampCountry) -> Unit, + val onDismiss: () -> Unit, ) interface Factory : ComponentFactory diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index 62dd39d0dd..0a88231cc3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -66,11 +66,17 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( config: ConfirmResidencyBottomSheetConfig, componentContext: ComponentContext, ): ComposableBottomSheetComponent = when (config) { - ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create( + is ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create( context = childByContext(componentContext), params = SelectCountryComponent.Params( cryptoCurrency = params.cryptoCurrency, - onDismiss = model.bottomSheetNavigation::dismiss, + onDismiss = { + // Dismiss country select sheet + model.bottomSheetNavigation.dismiss() + + // Dismiss confirm residency sheet + config.onDismiss() + }, ), ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt index baacb6b92a..0900dfebc2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/entity/ConfirmResidencyBottomSheetConfig.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable internal sealed class ConfirmResidencyBottomSheetConfig { @Serializable - data object SelectCountry : ConfirmResidencyBottomSheetConfig() + data class SelectCountry(val onDismiss: () -> Unit) : ConfirmResidencyBottomSheetConfig() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 5237af3c2b..f622253ea1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -10,6 +10,7 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent +import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM @@ -35,7 +36,7 @@ internal class ConfirmResidencyModel @Inject constructor( country = params.country.name, countryFlagUrl = params.country.image, isCountrySupported = params.country.onrampAvailable, - primaryButtonConfig = getPrimaryButtonConfig(), + primaryButtonConfig = getPrimaryButtonConfig(params.country), secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( onClick = ::onChangeClick, text = resourceReference(R.string.common_change), @@ -47,11 +48,12 @@ internal class ConfirmResidencyModel @Inject constructor( analyticsEventHandler.send(OnrampAnalyticsEvent.ResidenceConfirmScreenOpened(params.country.name)) } - private fun getPrimaryButtonConfig() = if (params.country.onrampAvailable) { + private fun getPrimaryButtonConfig(country: OnrampCountry) = if (country.onrampAvailable) { ConfirmResidencyUM.ActionButtonConfig( onClick = { - analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(params.country.name)) - modelScope.launch { saveDefaultCountryUseCase.invoke(params.country) } + analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name)) + modelScope.launch { saveDefaultCountryUseCase.invoke(country) } + params.onDismiss() }, text = resourceReference(R.string.common_confirm), ) @@ -67,6 +69,6 @@ internal class ConfirmResidencyModel @Inject constructor( private fun onChangeClick() { analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceChange) - bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry) + bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry(params.onDismiss)) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt index 739d1a4d6e..5164c91d5b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -58,7 +58,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor( country = config.country, onDismiss = { model.bottomSheetNavigation.dismiss() - model.handleOnrampAvailable(it.defaultCurrency) + model.handleOnrampAvailable() }, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index ef27622623..865ec89ec6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -19,7 +19,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.onramp.* import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.model.OnrampAvailability -import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.model.OnrampProviderWithQuote import com.tangem.domain.onramp.model.OnrampQuote import com.tangem.domain.onramp.model.error.OnrampError @@ -105,8 +104,7 @@ internal class OnrampMainComponentModel @Inject constructor( ) } - fun handleOnrampAvailable(currency: OnrampCurrency) { - _state.update { stateFactory.getReadyState(currency) } + fun handleOnrampAvailable() { subscribeToCountryAndCurrencyUpdates() subscribeToQuotesUpdate() } @@ -125,7 +123,7 @@ internal class OnrampMainComponentModel @Inject constructor( private fun handleOnrampAvailability(availability: OnrampAvailability) { when (availability) { - is OnrampAvailability.Available -> handleOnrampAvailable(availability.currency) + is OnrampAvailability.Available -> handleOnrampAvailable() is OnrampAvailability.ConfirmResidency, is OnrampAvailability.NotSupported, -> bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.ConfirmResidency(availability.country)) @@ -139,7 +137,13 @@ internal class OnrampMainComponentModel @Inject constructor( ifLeft = ::handleOnrampError, ifRight = { country -> if (country == null) return@onEach - _state.update { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) } + _state.update { + if (it is OnrampMainComponentUM.InitialLoading) { + stateFactory.getReadyState(country.defaultCurrency) + } else { + amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) + } + } updatePairsAndQuotes() }, ) From 4b4f11266530a3965c49c3e01999b4f686d5c1c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Dec 2024 21:31:38 +0300 Subject: [PATCH 19/40] Updated on 2026-08-14 --- core/res/src/main/res/values-uk-rUA/strings.xml | 3 +++ 1 file changed, 3 insertions(+) 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 7a2ba44136..e93c5799e5 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -12,6 +12,7 @@ Немає доступних токенів для обміну з обраним токеном. Будь ласка, оберіть інший. Немає вільної пари Щоб скористатися функцією обміну, у вашому портфелі має бути щонайменше 2 токени. + Додайте токени У вас додано лише 1 токен. Щоб скористатися функцією обміну, потрібно додати мінімум 2 токени. Додайте токени Оберіть токен для отримання @@ -324,6 +325,7 @@ Розкажіть, будь ласка, яку картку або кільце ви маєте? Привіт, команда підтримки, Будь ласка, розкажіть нам більше про вашу проблему. Кожна дрібниця може допомогти. + Раніше активований гаманець Мої пропозиції Не вдається відсканувати картку/кільце Звернення в підтримку @@ -598,6 +600,7 @@ Пошук за країною Недоступно Пошук по валюті + Використовуючи сервіс покупки, ви погоджуєтесь з %1$s та %2$s Сума покупки не може бути більше ніж %s Сума покупки повинна бути не менше %s Для данної валюти немає доступних провайдерів From 5c8102a75a52a1213b9b4bb1533ae5e574394732 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 14:42:20 +0300 Subject: [PATCH 20/40] 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 c4feab6dbf..f7af0b38fc 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": "ONRAMP_ENABLED", - "version": "5.19.0" + "version": "undefined" }, { "name": "MAIN_ACTION_BUTTONS_ENABLED", From 47f9a36fb4b3dc00b2139b7acfc9e617370c0da9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 17:01:05 +0500 Subject: [PATCH 21/40] Updated on 2026-08-14 --- common/ui/build.gradle.kts | 2 ++ .../ui/expressStatus}/ExpressEstimate.kt | 6 ++-- .../ui/expressStatus}/ExpressProvider.kt | 4 +-- .../expressStatus/ExpressStatusBottomSheet.kt | 24 ++++++++++++++ .../ui/expressStatus}/ExpressStatusItem.kt | 4 +-- .../ui/expressStatus}/ExpressStatusItems.kt | 10 +++--- .../OnrampStatusBottomSheetContent.kt | 10 ++---- .../state}/ExpressTransactionStateUM.kt | 31 ++++--------------- .../tokendetails/state/TokenDetailsState.kt | 2 +- .../tokendetails/state/express/ExchangeUM.kt | 21 +++++++++++++ ...nDetailsOnrampTransactionStateConverter.kt | 6 ++-- ...enDetailsSwapTransactionsStateConverter.kt | 20 ++++++------ .../factory/express/ExchangeStatusFactory.kt | 12 +++---- .../factory/express/ExpressStatusFactory.kt | 22 +++++++------ .../factory/express/OnrampStatusFactory.kt | 4 +-- .../tokendetails/ui/TokenDetailsScreen.kt | 4 +-- .../express/ExpressStatusBottomSheet.kt | 13 +++----- .../ExchangeStatusBottomSheetContent.kt | 8 ++--- .../viewmodels/TokenDetailsViewModel.kt | 5 ++- 19 files changed, 115 insertions(+), 93 deletions(-) rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express => common/ui/src/main/java/com/tangem/common/ui/expressStatus}/ExpressEstimate.kt (93%) rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express => common/ui/src/main/java/com/tangem/common/ui/expressStatus}/ExpressProvider.kt (97%) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express => common/ui/src/main/java/com/tangem/common/ui/expressStatus}/ExpressStatusItem.kt (98%) rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express => common/ui/src/main/java/com/tangem/common/ui/expressStatus}/ExpressStatusItems.kt (78%) rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp => common/ui/src/main/java/com/tangem/common/ui/expressStatus}/OnrampStatusBottomSheetContent.kt (79%) rename {features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express => common/ui/src/main/java/com/tangem/common/ui/expressStatus/state}/ExpressTransactionStateUM.kt (53%) create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts index 315574334e..28e0387959 100644 --- a/common/ui/build.gradle.kts +++ b/common/ui/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(deps.compose.navigation) implementation(deps.compose.navigation.hilt) implementation(deps.compose.coil) + implementation(deps.compose.constraintLayout) /** Deps */ implementation(deps.kotlin.immutable.collections) @@ -34,6 +35,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.transaction.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.onramp.models) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt similarity index 93% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt index 575ea130a4..85522dd450 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -8,16 +8,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import com.tangem.common.ui.R import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.inputrow.InputRowApprox import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.tokendetails.impl.R @Suppress("LongParameterList") @Composable -internal fun ExpressEstimate( +fun ExpressEstimate( timestamp: TextReference, fromTokenIconState: CurrencyIconState, toTokenIconState: CurrencyIconState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt similarity index 97% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt index 860204ee74..c83e14ae0a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import android.content.res.Configuration import android.widget.Toast @@ -29,7 +29,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @Composable -internal fun ExpressProvider( +fun ExpressProvider( providerName: TextReference, providerType: TextReference, providerTxId: String?, diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt new file mode 100644 index 0000000000..98c0bfce00 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt @@ -0,0 +1,24 @@ +package com.tangem.common.ui.expressStatus + +import androidx.compose.runtime.Composable +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme + +data class ExpressStatusBottomSheetConfig( + val value: ExpressTransactionStateUM, +) : TangemBottomSheetConfigContent + +@Composable +fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.tertiary, + ) { content: ExpressStatusBottomSheetConfig -> + when (val state = content.value) { + is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state) + } + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt similarity index 98% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt index 698c5aa938..25b2c3389d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import android.content.res.Configuration import androidx.annotation.DrawableRes @@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.constraintlayout.compose.* +import com.tangem.common.ui.R import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.currency.icon.CurrencyIcon @@ -27,7 +28,6 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.tokendetails.impl.R @Suppress("DestructuringDeclarationWithTooManyEntries", "LongMethod", "LongParameterList") @Composable diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt similarity index 78% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt index ffd74a9e24..1424805a32 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt @@ -1,14 +1,14 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier +import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.PersistentList -internal fun LazyListScope.expressTransactionsItems( +fun LazyListScope.expressTransactionsItems( expressTxs: PersistentList, modifier: Modifier = Modifier, ) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt similarity index 79% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt index bf278974e7..ce513e4e50 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -8,8 +8,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import com.tangem.common.ui.expressStatus.ExpressStatusBlock -import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 import com.tangem.core.ui.components.SpacerH12 @@ -17,12 +16,9 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider @Composable -internal fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) { +fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) { Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt similarity index 53% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt index bb94a805d3..45138854f8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt @@ -1,31 +1,13 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.state.express +package com.tangem.common.ui.expressStatus.state -import com.tangem.common.ui.expressStatus.state.ExpressStatusUM import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.onramp.model.OnrampStatus -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.feature.swap.domain.models.domain.ExchangeStatus -import com.tangem.feature.swap.domain.models.domain.SwapProvider -import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications -import kotlinx.collections.immutable.ImmutableList -internal sealed class ExpressTransactionStateUM { +interface ExpressTransactionStateUM { - abstract val info: ExpressTransactionStateInfoUM - - data class ExchangeUM( - override val info: ExpressTransactionStateInfoUM, - val provider: SwapProvider, - val activeStatus: ExchangeStatus?, - val statuses: ImmutableList, - val notification: ExchangeStatusNotifications? = null, - val showProviderLink: Boolean, - val isRefundTerminalStatus: Boolean, - val fromCryptoCurrency: CryptoCurrency, - val toCryptoCurrency: CryptoCurrency, - ) : ExpressTransactionStateUM() + val info: ExpressTransactionStateInfoUM data class OnrampUM( override val info: ExpressTransactionStateInfoUM, @@ -34,10 +16,10 @@ internal sealed class ExpressTransactionStateUM { val providerType: String, // todo onramp fix after SwapProvider moved to own module val activeStatus: OnrampStatus.Status, val fromCurrencyCode: String, - ) : ExpressTransactionStateUM() + ) : ExpressTransactionStateUM } -internal data class ExpressTransactionStateInfoUM( +data class ExpressTransactionStateInfoUM( val title: TextReference, val status: ExpressStatusUM, val notification: NotificationUM?, @@ -49,7 +31,6 @@ internal data class ExpressTransactionStateInfoUM( val onGoToProviderClick: (String) -> Unit, val onClick: () -> Unit, val iconState: ExpressTransactionStateIconUM, - val toAmount: TextReference, val toFiatAmount: TextReference?, val toAmountSymbol: String, @@ -61,7 +42,7 @@ internal data class ExpressTransactionStateInfoUM( val fromCurrencyIcon: CurrencyIconState, ) -internal enum class ExpressTransactionStateIconUM { +enum class ExpressTransactionStateIconUM { Warning, Error, None, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index d285c89fc9..06fda7a3b6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -9,7 +9,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt new file mode 100644 index 0000000000..4420f4be4a --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state.express + +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.SwapProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications +import kotlinx.collections.immutable.ImmutableList + +internal data class ExchangeUM( + override val info: ExpressTransactionStateInfoUM, + val provider: SwapProvider, + val activeStatus: ExchangeStatus?, + val statuses: ImmutableList, + val notification: ExchangeStatusNotifications? = null, + val showProviderLink: Boolean, + val isRefundTerminalStatus: Boolean, + val fromCryptoCurrency: CryptoCurrency, + val toCryptoCurrency: CryptoCurrency, +) : ExpressTransactionStateUM \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt index dd8b9bc4e9..9fc0a051b5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt @@ -23,9 +23,9 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateInfoUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index d4738e748b..18fe759bac 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -23,9 +23,9 @@ import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListMode import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeStatusState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateInfoUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider @@ -45,20 +45,20 @@ internal class TokenDetailsSwapTransactionsStateConverter( private val cryptoCurrency: CryptoCurrency, private val analyticsEventsHandler: AnalyticsEventHandler, appCurrencyProvider: Provider, -) : Converter> { +) : Converter> { private val iconStateConverter = CryptoCurrencyToIconStateConverter() private val appCurrency = appCurrencyProvider() - override fun convert(value: Unit): PersistentList { + override fun convert(value: Unit): PersistentList { return persistentListOf() } fun convert( savedTransactions: List, quotes: Set, - ): PersistentList { - val result = mutableListOf() + ): PersistentList { + val result = mutableListOf() savedTransactions .forEach { swapTransaction -> @@ -84,7 +84,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null) val showProviderLink = getShowProviderLink(notifications, transaction.status) result.add( - ExpressTransactionStateUM.ExchangeUM( + ExchangeUM( provider = transaction.provider, statuses = getStatuses(transaction.status?.status), notification = notifications, @@ -108,11 +108,11 @@ internal class TokenDetailsSwapTransactionsStateConverter( } fun updateTxStatus( - tx: ExpressTransactionStateUM.ExchangeUM, + tx: ExchangeUM, statusModel: ExchangeStatusModel?, refundToken: CryptoCurrency?, isRefundTerminalStatus: Boolean, - ): ExpressTransactionStateUM.ExchangeUM { + ): ExchangeUM { if (statusModel == null || tx.activeStatus == statusModel.status) { Timber.e("UpdateTxStatus isn't required. Current status isn't changed") return tx diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt index 545ff398df..25d87bd106 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore @@ -15,9 +16,8 @@ import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted @@ -55,7 +55,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( ) } - suspend operator fun invoke(): Flow> { + suspend operator fun invoke(): Flow> { val selectedWallet = getSelectedWalletSyncUseCase().fold( ifLeft = { return emptyFlow() }, ifRight = { it }, @@ -82,7 +82,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( suspend fun removeTransactionOnBottomSheetClosed(isForceTerminal: Boolean = false) { val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return - val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.ExchangeUM ?: return + val selectedTx = bottomSheetConfig.value as? ExchangeUM ?: return val shouldTerminate = selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus) || isForceTerminal if (shouldTerminate) { @@ -95,7 +95,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( } } - suspend fun updateSwapTxStatus(swapTx: ExpressTransactionStateUM.ExchangeUM): ExpressTransactionStateUM.ExchangeUM { + suspend fun updateSwapTxStatus(swapTx: ExchangeUM): ExchangeUM { return if (swapTx.activeStatus.isTerminal(swapTx.isRefundTerminalStatus)) { swapTx } else { @@ -163,7 +163,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( private fun getExchangeStatusState( savedTransactions: List?, quotes: Set, - ): PersistentList { + ): PersistentList { if (savedTransactions == null) { return persistentListOf() } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index aa5d0ecc92..0ed77a77b5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.domain.appcurrency.model.AppCurrency @@ -10,8 +12,7 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -79,11 +80,13 @@ internal class ExpressStatusFactory @AssistedInject constructor( expressTxs.map { tx -> async { when (tx) { - is ExpressTransactionStateUM.ExchangeUM -> exchangeStatusFactory.updateSwapTxStatus(tx) + is ExchangeUM -> exchangeStatusFactory.updateSwapTxStatus(tx) is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.updateOnrmapTxStatus(tx) + else -> null } } }.awaitAll() + .filterNotNull() .toPersistentList() } @@ -95,13 +98,13 @@ internal class ExpressStatusFactory @AssistedInject constructor( val config = state.bottomSheetConfig val expressBottomSheet = config?.content as? ExpressStatusBottomSheetConfig val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId } - if (currentTx is ExpressTransactionStateUM.ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) { + if (currentTx is ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) { updateBalance(currentTx.toCryptoCurrency) } val expressTxsToDisplay = expressTxs.filterNot { when (it) { - is ExpressTransactionStateUM.ExchangeUM -> false - is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden + is ExpressTransactionStateUM.OnrampUM -> false // it.activeStatus.isHidden + else -> false } }.toPersistentList() return state.copy( @@ -115,7 +118,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( fun getStateWithExpressStatusBottomSheet(expressState: ExpressTransactionStateUM): TokenDetailsState { val analyticEvent = when (expressState) { - is ExpressTransactionStateUM.ExchangeUM -> TokenExchangeAnalyticsEvent.CexTxStatusOpened( + is ExchangeUM -> TokenExchangeAnalyticsEvent.CexTxStatusOpened( cryptoCurrency.symbol, ) is ExpressTransactionStateUM.OnrampUM -> TokenOnrampAnalyticsEvent.OnrampStatusOpened( @@ -123,6 +126,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( provider = expressState.providerName, fiatCurrency = expressState.fromCurrencyCode, ) + else -> return currentStateProvider() } analyticsEventsHandler.send(analyticEvent) @@ -156,9 +160,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( isForceTerminal: Boolean = false, ) { when (expressState) { - is ExpressTransactionStateUM.ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed( - isForceTerminal, - ) + is ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed(isForceTerminal) is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.removeTransactionOnBottomSheetClosed() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index 3e2cbe8813..7ca14864da 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus import com.tangem.domain.appcurrency.model.AppCurrency @@ -14,9 +16,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index a522838fe3..87ec82270c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -22,6 +22,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.paging.compose.collectAsLazyPagingItems +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.expressTransactionsItems import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet @@ -49,8 +51,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.expressTransactionsItems import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock import com.tangem.features.markets.token.block.TokenMarketBlockComponent diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt index c37f0c020a..8eec7243af 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt @@ -1,17 +1,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express import androidx.compose.runtime.Composable +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.OnrampStatusBottomSheetContent +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetContent -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheetContent - -internal data class ExpressStatusBottomSheetConfig( - val value: ExpressTransactionStateUM, -) : TangemBottomSheetConfigContent @Composable internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { @@ -21,7 +18,7 @@ internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { ) { content: ExpressStatusBottomSheetConfig -> when (val state = content.value) { is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state) - is ExpressTransactionStateUM.ExchangeUM -> ExchangeStatusBottomSheetContent(state) + is ExchangeUM -> ExchangeStatusBottomSheetContent(state) } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt index 305ac6bb39..ddf1a957dd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import com.tangem.common.ui.expressStatus.ExpressEstimate +import com.tangem.common.ui.expressStatus.ExpressProvider import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 import com.tangem.core.ui.components.SpacerH12 @@ -19,12 +21,10 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM @Composable -internal fun ExchangeStatusBottomSheetContent(state: ExpressTransactionStateUM.ExchangeUM) { +internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) { Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 6690f0a085..d32e514fac 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.* import androidx.paging.cachedIn import arrow.core.getOrElse import com.tangem.blockchain.common.address.AddressType -import com.tangem.common.core.TangemSdkError import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.routing.bundle.unbundle @@ -68,10 +67,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.Token import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenDetailsNotificationsAnalyticsSender import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider From 529527cad0071b64840e78b3b77354848979ef20 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 17:04:38 +0500 Subject: [PATCH 22/40] Updated on 2026-08-14 --- .../factory/express/ExpressStatusFactory.kt | 2 +- features/wallet/impl/build.gradle.kts | 7 +- .../wallet/domain/OnrampStatusFactory.kt | 99 +++++++ .../implementors/SingleWalletContentLoader.kt | 14 + .../SingleWalletContentLoaderFactory.kt | 6 + .../wallet/state/model/WalletState.kt | 3 + .../SetExpressStatusesTransformer.kt | 76 +++++ .../SingleWalletOnrampTransactionConverter.kt | 279 ++++++++++++++++++ .../state/utils/WalletLoadingStateFactory.kt | 2 + .../SingleWalletExpressStatusesSubscriber.kt | 92 ++++++ .../presentation/wallet/ui/WalletScreen.kt | 10 + .../wallet/viewmodels/WalletViewModel.kt | 28 +- .../intents/WalletContentClickIntents.kt | 47 +++ 13 files changed, 659 insertions(+), 6 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 0ed77a77b5..7cfbd6eb7b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -103,7 +103,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( } val expressTxsToDisplay = expressTxs.filterNot { when (it) { - is ExpressTransactionStateUM.OnrampUM -> false // it.activeStatus.isHidden + is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden else -> false } }.toPersistentList() diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 88d3ff2a2c..2aa0052b0a 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -52,18 +52,19 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.analytics) implementation(projects.core.analytics.models) - implementation(projects.common.routing) implementation(projects.core.deepLinks) implementation(projects.core.deepLinks.global) implementation(projects.core.decompose) + implementation(projects.core.datasource) + implementation(projects.libs.crypto) + implementation(projects.libs.blockchainSdk) /** Domain modules */ implementation(projects.domain.card) implementation(projects.domain.demo) implementation(projects.domain.legacy) - implementation(projects.libs.blockchainSdk) implementation(projects.domain.models) implementation(projects.domain.settings) implementation(projects.domain.tokens) @@ -82,6 +83,7 @@ dependencies { implementation(projects.domain.markets.models) implementation(projects.domain.feedback) implementation(projects.domain.onramp.models) + implementation(projects.domain.onramp) //TODO: Create api/impl modules for onboarding [REDACTED_JIRA] implementation(projects.features.onboarding) @@ -100,6 +102,7 @@ dependencies { /** Common modules */ implementation(projects.common.ui) + implementation(projects.common.routing) /** Test libraries */ implementation(deps.test.junit) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt new file mode 100644 index 0000000000..8387de10c1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt @@ -0,0 +1,99 @@ +package com.tangem.feature.wallet.presentation.wallet.domain + +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus +import com.tangem.domain.onramp.GetOnrampStatusUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.OnrampUpdateTransactionStatusUseCase +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.OnrampStatus.Status.* +import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject + +@ViewModelScoped +internal class OnrampStatusFactory @Inject constructor( + private val stateHolder: WalletStateController, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, + private val getOnrampStatusUseCase: GetOnrampStatusUseCase, + private val onrampUpdateTransactionStatusUseCase: OnrampUpdateTransactionStatusUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend fun removeTransactionOnBottomSheetClosed() { + val state = stateHolder.getSelectedWallet() + val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return + val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return + + if (selectedTx.activeStatus.isTerminal) { + onrampRemoveTransactionUseCase(externalTxId = selectedTx.info.txExternalId) + } + } + + suspend fun updateOnrmapTransactionStatuses() = withContext(dispatchers.io) { + val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content + ?: return@withContext + + singleWalletState.expressTxs.map { tx -> + async { + if (tx is ExpressTransactionStateUM.OnrampUM) { + updateOnrampTxStatus(tx) + } + } + }.awaitAll() + } + + private suspend fun updateOnrampTxStatus(onrampTx: ExpressTransactionStateUM.OnrampUM) { + if (!onrampTx.activeStatus.isTerminal) { + getOnrampStatusUseCase(onrampTx.info.txId).fold( + ifLeft = { + Timber.e("Couldn't update onramp status. $it") + }, + ifRight = { statusModel -> + val externalTxId = statusModel.externalTxId + val status = toAnalyticStatus(statusModel.status) ?: return + + if (statusModel.status != onrampTx.activeStatus) { + analyticsEventHandler.send( + TokenOnrampAnalyticsEvent.OnrampStatusChanged( + tokenSymbol = onrampTx.info.toAmountSymbol, + status = status.name, + provider = onrampTx.providerName, + fiatCurrency = onrampTx.fromCurrencyCode, + ), + ) + onrampUpdateTransactionStatusUseCase(externalTxId = externalTxId, statusModel.status) + } + }, + ) + } + } + + private fun toAnalyticStatus(status: OnrampStatus.Status?): ExpressAnalyticsStatus? { + return when (status) { + Expired, + Paused, + -> ExpressAnalyticsStatus.Cancelled + Created, + WaitingForPayment, + PaymentProcessing, + Paid, + Sending, + -> ExpressAnalyticsStatus.InProgress + Verifying -> ExpressAnalyticsStatus.KYC + Failed -> ExpressAnalyticsStatus.Fail + Finished -> ExpressAnalyticsStatus.Done + null -> null + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index b4c0f3252e..0d96fb7855 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase @@ -27,6 +29,8 @@ internal class SingleWalletContentLoader( private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) : WalletContentLoader(id = userWallet.walletId) { @@ -55,6 +59,16 @@ internal class SingleWalletContentLoader( getSingleWalletWarningsFactory = getSingleWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), + SingleWalletExpressStatusesSubscriber( + userWallet = userWallet, + stateHolder = stateHolder, + clickIntents = clickIntents, + getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + analyticsEventHandler = analyticsEventHandler, + getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, + onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, + ), TxHistorySubscriber( userWallet = userWallet, isRefresh = isRefresh, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt index c6be994278..aa40c79e7f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase @@ -26,6 +28,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { @@ -45,6 +49,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, analyticsEventHandler = analyticsEventHandler, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, + onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index 6a23ccc059..4f86e6685c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TxHistoryState @@ -59,6 +60,8 @@ internal sealed interface WalletState : WalletStateHolder { override val buttons: PersistentList, override val marketPriceBlockState: MarketPriceBlockState, override val txHistoryState: TxHistoryState, + val expressTxsToDisplay: PersistentList, + val expressTxs: PersistentList, ) : SingleCurrency() data class Locked( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt new file mode 100644 index 0000000000..98058d6c2e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt @@ -0,0 +1,76 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletOnrampTransactionConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class SetExpressStatusesTransformer( + userWalletId: UserWalletId, + private val onrampTxs: List, + private val clickIntents: WalletClickIntents, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val appCurrency: AppCurrency, + private val analyticsEventHandler: AnalyticsEventHandler, +) : WalletStateTransformer(userWalletId) { + override fun transform(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.SingleCurrency.Content -> { + val expressTxs = SingleWalletOnrampTransactionConverter( + clickIntents = clickIntents, + cryptoCurrencyStatus = cryptoCurrencyStatus, + appCurrency = appCurrency, + analyticsEventHandler = analyticsEventHandler, + ).convertList(onrampTxs).toPersistentList() + + val expressTxsToDisplay = expressTxs.filterNot { + it.activeStatus.isHidden + }.toPersistentList() + + val expressBottomSheet = prevState.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig + val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId } + + prevState.copy( + expressTxs = expressTxs, + expressTxsToDisplay = expressTxsToDisplay, + bottomSheetConfig = prevState.bottomSheetConfig?.updateStateWithExpressStatusBottomSheet(currentTx), + ) + } + is WalletState.SingleCurrency.Locked -> { + Timber.w("Impossible to load express statuses for locked wallet") + prevState + } + is WalletState.Visa -> { + Timber.w("Impossible to load express statuses for visa wallet") + prevState + } + is WalletState.MultiCurrency -> { + Timber.w("Impossible to load express statuses for multi-currency wallet") + prevState + } + } + } + + private fun TangemBottomSheetConfig.updateStateWithExpressStatusBottomSheet( + expressState: ExpressTransactionStateUM?, + ): TangemBottomSheetConfig { + val currentConfig = this.content as? ExpressStatusBottomSheetConfig ?: return this + if (expressState == null) return this + return copy( + content = if (currentConfig.value != expressState) { + ExpressStatusBottomSheetConfig(expressState) + } else { + currentConfig + }, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt new file mode 100644 index 0000000000..f6443b6ed5 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt @@ -0,0 +1,279 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.common.ui.notifications.ExpressNotificationsUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent +import com.tangem.feature.wallet.impl.R +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf + +internal class SingleWalletOnrampTransactionConverter( + private val clickIntents: WalletClickIntents, + cryptoCurrencyStatus: CryptoCurrencyStatus, + private val appCurrency: AppCurrency, + private val analyticsEventHandler: AnalyticsEventHandler, +) : Converter { + + private val iconStateConverter = CryptoCurrencyToIconStateConverter() + + private val currency = cryptoCurrencyStatus.currency + private val status = cryptoCurrencyStatus.value + + override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM { + return ExpressTransactionStateUM.OnrampUM( + info = ExpressTransactionStateInfoUM( + title = resourceReference( + id = R.string.express_status_buying, + wrappedList(currency.name), + ), + status = convertStatuses(value.status, value.externalTxUrl), + notification = getNotification(value.status, value.externalTxUrl, value.providerName), + txId = value.txId, + txExternalId = value.externalTxId, + txExternalUrl = value.externalTxUrl, + timestamp = value.timestamp, + timestampFormatted = resourceReference( + R.string.send_date_format, + wrappedList( + value.timestamp.toDateFormatWithTodayYesterday(), + value.timestamp.toTimeFormat(), + ), + ), + toAmount = stringReference( + value.toAmount.format { crypto(currency) }, + ), + toFiatAmount = stringReference( + status.fiatRate?.multiply(value.toAmount).format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + toAmountSymbol = currency.symbol, + toCurrencyIcon = iconStateConverter.convert(currency), + fromAmount = stringReference( + value.fromAmount.format { + fiat( + fiatCurrencyCode = value.fromCurrency.name, + fiatCurrencySymbol = value.fromCurrency.code, + ) + }, + ), + fromFiatAmount = null, + fromAmountSymbol = value.fromCurrency.code, + fromCurrencyIcon = CurrencyIconState.FiatIcon( + url = value.fromCurrency.image, + fallbackResId = R.drawable.ic_currency_24, + ), + iconState = getIconState(value.status), + onGoToProviderClick = { + analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) + clickIntents.onGoToProviderClick(it) + }, + onClick = { + val analyticEvent = TokenOnrampAnalyticsEvent.OnrampStatusOpened( + tokenSymbol = currency.symbol, + provider = value.providerName, + fiatCurrency = value.fromCurrency.code, + ) + analyticsEventHandler.send(analyticEvent) + clickIntents.onExpressTransactionClick(value.txId) + }, + ), + providerName = value.providerName, + providerImageUrl = value.providerImageUrl, + providerType = value.providerType, + activeStatus = value.status, + fromCurrencyCode = value.fromCurrency.code, + ) + } + + private fun getNotification( + status: OnrampStatus.Status, + externalTxUrl: String?, + providerName: String, + ): NotificationUM? { + if (externalTxUrl == null) return null + return when (status) { + OnrampStatus.Status.Verifying -> { + analyticsEventHandler.send( + TokenOnrampAnalyticsEvent.NoticeKYC(currency.symbol, providerName), + ) + ExpressNotificationsUM.NeedVerification { + clickIntents.onGoToProviderClick(externalTxUrl) + } + } + OnrampStatus.Status.Failed -> { + ExpressNotificationsUM.FailedByProvider { + clickIntents.onGoToProviderClick(externalTxUrl) + } + } + else -> null + } + } + + private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM { + return when (status) { + OnrampStatus.Status.Verifying -> ExpressTransactionStateIconUM.Warning + OnrampStatus.Status.Failed -> ExpressTransactionStateIconUM.Error + else -> ExpressTransactionStateIconUM.None + } + } + + private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM { + val statuses = with(status) { + persistentListOf( + getAwaitingDepositItem(), + getPaymentProcessingItem(), + getBuyingItem(), + getSendingItem(), + ) + } + + return ExpressStatusUM( + title = resourceReference(R.string.common_transaction_status), + link = getStatusLink(status, externalTxUrl), + statuses = statuses, + ) + } + + private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.WaitingForPayment.order -> { + resourceReference(R.string.express_exchange_status_receiving) + } + this == OnrampStatus.Status.WaitingForPayment -> { + resourceReference(R.string.express_exchange_status_receiving_active) + } + else -> { + resourceReference(R.string.express_exchange_status_received) + } + }, + state = getStatusState(OnrampStatus.Status.WaitingForPayment), + ) + + private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + resourceReference(R.string.express_exchange_status_confirming) + } + this == OnrampStatus.Status.PaymentProcessing -> { + resourceReference(R.string.express_exchange_status_confirming_active) + } + this == OnrampStatus.Status.Verifying -> { + resourceReference(R.string.express_exchange_status_verifying) + } + this == OnrampStatus.Status.Failed -> { + resourceReference(R.string.express_exchange_status_failed) + } + else -> resourceReference(R.string.express_exchange_status_confirmed) + }, + state = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + ExpressStatusItemState.Default + } + this == OnrampStatus.Status.PaymentProcessing -> { + ExpressStatusItemState.Active + } + this == OnrampStatus.Status.Verifying -> { + ExpressStatusItemState.Warning + } + this == OnrampStatus.Status.Failed -> { + ExpressStatusItemState.Error + } + else -> { + ExpressStatusItemState.Done + } + }, + ) + + private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Paid.order -> { + resourceReference(R.string.express_status_buying, wrappedList(currency.name)) + } + this == OnrampStatus.Status.Paid -> { + resourceReference( + R.string.express_status_buying_active, + wrappedList(currency.name), + ) + } + else -> { + resourceReference(R.string.express_status_bought, wrappedList(currency.name)) + } + }, + state = getStatusState(OnrampStatus.Status.Paid), + ) + + private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Sending.order -> { + resourceReference( + R.string.express_exchange_status_sending, + wrappedList(currency.name), + ) + } + this == OnrampStatus.Status.Sending -> { + resourceReference( + R.string.express_exchange_status_sending_active, + wrappedList(currency.name), + ) + } + else -> { + resourceReference( + R.string.express_exchange_status_sent, + wrappedList(currency.name), + ) + } + }, + state = getStatusState(OnrampStatus.Status.Sending), + ) + + private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM { + if (externalTxUrl == null) return ExpressLinkUM.Empty + return when (status) { + OnrampStatus.Status.Verifying, + OnrampStatus.Status.Failed, + -> { + ExpressLinkUM.Content( + icon = R.drawable.ic_arrow_top_right_24, + text = resourceReference(R.string.common_go_to_provider), + onClick = { + clickIntents.onGoToProviderClick(externalTxUrl) + }, + ) + } + else -> ExpressLinkUM.Empty + } + } + + private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when { + order < targetState.order -> ExpressStatusItemState.Default + this == targetState -> ExpressStatusItemState.Active + else -> ExpressStatusItemState.Done + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index aa619fe1e2..45b1b98237 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -58,6 +58,8 @@ internal class WalletLoadingStateFactory( value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), ), ), + expressTxsToDisplay = persistentListOf(), + expressTxs = persistentListOf(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt new file mode 100644 index 0000000000..37eb922c67 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt @@ -0,0 +1,92 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import arrow.core.Either +import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetExpressStatusesTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* +import timber.log.Timber + +@Suppress("LongParameterList") +internal class SingleWalletExpressStatusesSubscriber( + private val userWallet: UserWallet, + private val stateHolder: WalletStateController, + private val clickIntents: WalletClickIntents, + private val analyticsEventHandler: AnalyticsEventHandler, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, +) : WalletSubscriber() { + + override fun create( + coroutineScope: CoroutineScope, + ): Flow, AppCurrency>> { + return combine( + flow = getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId) + .conflate() + .distinctUntilChanged(), + flow2 = getSelectedAppCurrencyUseCase() + .conflate() + .distinctUntilChanged() + .map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } }, + transform = { maybeCurrencyStatus, appCurrency -> maybeCurrencyStatus to appCurrency }, + ).onEach { maybeCurrencyStatusAndAppCurrency -> + val status = maybeCurrencyStatusAndAppCurrency.first.getOrElse { + Timber.e("Unable to get primary currency status: $it") + return@onEach + } + + getOnrampTransactionsUseCase( + userWalletId = userWallet.walletId, + cryptoCurrencyId = status.currency.id, + ).onEach { maybeTransaction -> + maybeTransaction.fold( + ifRight = { onrampTxs -> + onrampTxs.clearHiddenTerminal() + stateHolder.update( + SetExpressStatusesTransformer( + userWalletId = userWallet.walletId, + onrampTxs = onrampTxs, + clickIntents = clickIntents, + cryptoCurrencyStatus = status, + appCurrency = maybeCurrencyStatusAndAppCurrency.second, + analyticsEventHandler = analyticsEventHandler, + ), + ) + }, + ifLeft = { + stateHolder.update( + SetExpressStatusesTransformer( + userWalletId = userWallet.walletId, + onrampTxs = listOf(), + clickIntents = clickIntents, + cryptoCurrencyStatus = status, + appCurrency = maybeCurrencyStatusAndAppCurrency.second, + analyticsEventHandler = analyticsEventHandler, + ), + ) + }, + ) + } + .launchIn(coroutineScope) + } + } + + private suspend fun List.clearHiddenTerminal() { + this.filter { it.status.isHidden && it.status.isTerminal } + .forEach { onrampRemoveTransactionUseCase(externalTxId = it.externalTxId) } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 2617c1d2e8..52a0fea363 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -45,6 +45,9 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.expressTransactionsItems import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.components.atoms.handComposableComponentHeight import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig @@ -216,6 +219,12 @@ private fun WalletContent( walletState.marketPriceBlockState?.let { marketPriceBlockState -> marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) } + if (walletState is WalletState.SingleCurrency.Content) { + expressTransactionsItems( + expressTxs = walletState.expressTxsToDisplay, + modifier = itemModifier, + ) + } } (selectedWallet as? WalletState.Visa.Content)?.let { @@ -716,6 +725,7 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig) + is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a9af33b972..785d29d72e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -17,6 +17,7 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore +import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -33,9 +34,7 @@ import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.indexOfFirstOrNull import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay @@ -70,6 +69,7 @@ internal class WalletViewModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val walletImageResolver: WalletImageResolver, private val tokenListStore: MultiWalletTokenListStore, + private val onrampStatusFactory: OnrampStatusFactory, private val walletFeatureToggles: WalletFeatureToggles, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -81,6 +81,8 @@ internal class WalletViewModel @Inject constructor( private val refreshWalletJobHolder = JobHolder() private var needToRefreshWallet = false + private var expressTxStatusTaskScheduler = SingleTaskScheduler() + init { analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened) @@ -93,6 +95,7 @@ internal class WalletViewModel @Inject constructor( subscribeOnSelectedWalletFlow() subscribeToScreenBackgroundState() subscribeOnPushNotificationsPermission() + subscribeOnExpressTransactionsUpdates() } private fun maybeMigrateNames() { @@ -239,6 +242,24 @@ internal class WalletViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnExpressTransactionsUpdates() { + viewModelScope.launch(dispatchers.main) { + expressTxStatusTaskScheduler.cancelTask() + expressTxStatusTaskScheduler.scheduleTask( + viewModelScope, + PeriodicTask( + isDelayFirst = false, + delay = EXPRESS_STATUS_UPDATE_DELAY, + task = { + runCatching { onrampStatusFactory.updateOnrmapTransactionStatuses() } + }, + onSuccess = { /* no-op */ }, + onError = { /* no-op */ }, + ), + ) + } + } + private fun needToRefreshTimer() { viewModelScope.launch { delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS) @@ -430,5 +451,6 @@ internal class WalletViewModel @Inject constructor( private companion object { const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L + const val EXPRESS_STATUS_UPDATE_DELAY = 10000L } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index 32132d1dfc..0b20b60c75 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import arrow.core.getOrElse +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase @@ -11,10 +12,14 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped @@ -43,6 +48,12 @@ internal interface WalletContentClickIntents { fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) fun onTransactionClick(txHash: String) + + fun onDissmissBottomSheet() + + fun onGoToProviderClick(externalTxId: String) + + fun onExpressTransactionClick(txId: String) } @Suppress("LongParameterList") @@ -51,6 +62,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor, private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor, + private val onrampStatusFactory: OnrampStatusFactory, private val getUserWalletUseCase: GetUserWalletUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, @@ -164,4 +176,39 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ) } } + + override fun onGoToProviderClick(externalTxUrl: String) { + router.openUrl(externalTxUrl) + } + + override fun onDissmissBottomSheet() { + val userWalletId = stateHolder.getSelectedWalletId() + if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { + viewModelScope.launch(dispatchers.main) { + onrampStatusFactory.removeTransactionOnBottomSheetClosed() + } + } + stateHolder.update(CloseBottomSheetTransformer(userWalletId)) + } + + override fun onExpressTransactionClick(txId: String) { + viewModelScope.launch { + val userWalletId = stateHolder.getSelectedWalletId() + val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content + ?: return@launch + + val expressTransaction = singleWalletState.expressTxsToDisplay.firstOrNull { it.info.txId == txId } + ?: return@launch + + stateHolder.update( + OpenBottomSheetTransformer( + userWalletId = userWalletId, + content = ExpressStatusBottomSheetConfig( + value = expressTransaction, + ), + onDismissBottomSheet = ::onDissmissBottomSheet, + ), + ) + } + } } \ No newline at end of file From db0d566db289004429786ad5e153cf3934e6d54d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 17:34:43 +0300 Subject: [PATCH 23/40] Updated on 2026-08-14 --- .../middlewares/TradeCryptoMiddleware.kt | 35 ------ .../domain/tokens/legacy/TradeCryptoAction.kt | 27 ---- .../presentation/viewmodel/SendViewModel.kt | 3 +- .../viewmodels/TokenDetailsViewModel.kt | 70 ++--------- .../deeplink/WalletDeepLinksHandler.kt | 115 +++--------------- .../WalletCurrencyActionsClickIntents.kt | 69 ++--------- 6 files changed, 34 insertions(+), 285 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 9edfafa699..5b5d56d9d3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -49,8 +49,6 @@ object TradeCryptoMiddleware { is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) is TradeCryptoAction.Buy -> proceedBuyAction(state, action) is TradeCryptoAction.Sell -> proceedSellAction(action) - is TradeCryptoAction.SendToken -> handleNewSendToken(action = action) - is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action) } } @@ -166,37 +164,4 @@ object TradeCryptoMiddleware { transactionId = transactionId, )?.let { store.dispatchOpenUrl(it) } } - - private fun handleNewSendToken(action: TradeCryptoAction.SendToken) { - handleNewSend( - userWalletId = action.userWallet.walletId, - txInfo = action.transactionInfo, - currency = action.tokenCurrency, - ) - } - - private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) { - handleNewSend( - userWalletId = action.userWallet.walletId, - txInfo = action.transactionInfo, - currency = action.coinStatus.currency, - ) - } - - private fun handleNewSend( - userWalletId: UserWalletId, - txInfo: TradeCryptoAction.TransactionInfo?, - currency: CryptoCurrency, - ) { - val route = AppRoute.Send( - currency = currency, - userWalletId = userWalletId, - transactionId = txInfo?.transactionId, - destinationAddress = txInfo?.destinationAddress, - amount = txInfo?.amount, - tag = txInfo?.tag, - ) - - store.dispatchNavigationAction { push(route) } - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt index a9ffeda4c8..58b0a13e7f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt @@ -1,11 +1,9 @@ package com.tangem.domain.tokens.legacy import com.tangem.domain.onramp.model.OnrampSource -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import org.rekotlin.Action -import java.math.BigDecimal sealed class TradeCryptoAction : Action { @@ -23,29 +21,4 @@ sealed class TradeCryptoAction : Action { val cryptoCurrencyStatus: CryptoCurrencyStatus, val appCurrencyCode: String, ) : TradeCryptoAction() - - @Deprecated("Use AppRoute instead") - data class SendToken( - val userWallet: UserWallet, - val tokenCurrency: CryptoCurrency.Token, - val tokenFiatRate: BigDecimal?, - val coinFiatRate: BigDecimal?, - val feeCurrencyStatus: CryptoCurrencyStatus?, - val transactionInfo: TransactionInfo? = null, - ) : TradeCryptoAction() - - @Deprecated("Use AppRoute instead") - data class SendCoin( - val userWallet: UserWallet, - val coinStatus: CryptoCurrencyStatus, - val feeCurrencyStatus: CryptoCurrencyStatus?, - val transactionInfo: TransactionInfo? = null, - ) : TradeCryptoAction() - - data class TransactionInfo( - val transactionId: String, - val destinationAddress: String, - val amount: String, - val tag: String? = null, - ) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index cdd155d645..8b0778f2cd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -378,10 +378,9 @@ internal class SendViewModel @Inject constructor( when { uiState.value.sendState?.isSuccess == true -> return transactionId != null && amount != null && destinationAddress != null -> { - loadFee() uiState.value = stateFactory.getReadyState(amount, destinationAddress, memo) stateRouter.showSend() - updateNotifications() + loadFee() } else -> { uiState.value = stateFactory.getReadyState() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index d32e514fac..250f345a70 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -40,7 +40,6 @@ import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress @@ -83,7 +82,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber -import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList", "LargeClass", "TooManyFunctions") @@ -98,8 +96,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val getExploreUrlUseCase: GetExploreUrlUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, @@ -517,68 +513,16 @@ internal class TokenDetailsViewModel @Inject constructor( return } - sendCurrency(status = cryptoCurrencyStatus ?: return) + sendCurrency() } - private fun sendCurrency(status: CryptoCurrencyStatus, transactionInfo: TransactionInfo? = null) { - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull() + private fun sendCurrency() { + val route = AppRoute.Send( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + ) - when (val currency = status.currency) { - is CryptoCurrency.Coin -> { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = status, - feeCurrencyStatus = maybeFeeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } - is CryptoCurrency.Token -> { - sendToken( - tokenCurrency = currency, - tokenFiatRate = status.value.fiatRate, - feeCurrencyStatus = maybeFeeCurrencyStatus, - transactionInfo = transactionInfo, - ) - } - } - } - } - - private fun sendToken( - tokenCurrency: CryptoCurrency.Token, - tokenFiatRate: BigDecimal?, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo?, - ) { - viewModelScope.launch(dispatchers.main) { - val maybeCoinStatus = getNetworkCoinStatusUseCase( - userWalletId = userWalletId, - networkId = tokenCurrency.network.id, - derivationPath = tokenCurrency.network.derivationPath, - isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .conflate() - .distinctUntilChanged() - .firstOrNull() - - reduxStateHolder.dispatchWithMain( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = tokenCurrency, - tokenFiatRate = tokenFiatRate, - coinFiatRate = maybeCoinStatus?.fold( - ifLeft = { null }, - ifRight = { it.value.fiatRate }, - ), - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } + appRouter.push(route) } override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index d729196c4f..d58c9a80ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -2,25 +2,22 @@ package com.tangem.feature.wallet.presentation.deeplink import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLink import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.deeplink.global.SellCurrencyDeepLink -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.* -import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.onramp.OnrampFeatureToggles import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -28,13 +25,9 @@ internal class WalletDeepLinksHandler @Inject constructor( private val deepLinksRegistry: DeepLinksRegistry, private val analyticsEventHandler: AnalyticsEventHandler, private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val reduxStateHolder: ReduxStateHolder, private val clickIntents: WalletClickIntents, private val onrampFeatureToggles: OnrampFeatureToggles, + private val router: AppRouter, ) { private var deepLinksMap = mutableMapOf>() @@ -75,35 +68,23 @@ internal class WalletDeepLinksHandler @Inject constructor( } private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) { - val cryptoCurrencyStatus = findCryptoCurrencyStatus(userWallet, data.currencyId) ?: return - val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( - userWallet.walletId, - cryptoCurrencyStatus, - ).getOrNull() + val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId, data.currencyId).getOrNull() - val transactionInfo = data.let { - TransactionInfo( - amount = it.baseCurrencyAmount, - destinationAddress = it.depositWalletAddress, - transactionId = it.transactionId, - tag = it.depositWalletAddressTag, - ) + if (cryptoCurrency == null) { + Timber.e("onSellCurrencyDeepLink cryptoCurrency is null") + return } - when (cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> sendCoin( - userWallet = userWallet, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ) - is CryptoCurrency.Token -> sendToken( - userWallet = userWallet, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ) - } + val route = AppRoute.Send( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + transactionId = data.transactionId, + destinationAddress = data.depositWalletAddress, + amount = data.baseCurrencyAmount, + tag = data.depositWalletAddressTag, + ) + + router.push(route) } private suspend fun onBuyCurrencyDeepLink(externalTxId: String, userWallet: UserWallet) { @@ -114,62 +95,4 @@ internal class WalletDeepLinksHandler @Inject constructor( analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol)) } } - - private suspend fun findCryptoCurrencyStatus( - userWallet: UserWallet, - currencyIdValue: String, - ): CryptoCurrencyStatus? { - return if (userWallet.isMultiCurrency) { - getCryptoCurrencyStatusesSyncUseCase(userWallet.walletId).getOrNull()?.let { currencies -> - currencies.find { currencyIdValue == it.currency.id.value } - } - } else { - getCryptoCurrencyStatusSyncUseCase(userWallet.walletId).getOrNull() - } - } - - private fun sendCoin( - userWallet: UserWallet, - cryptoCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo, - ) { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } - - private suspend fun sendToken( - userWallet: UserWallet, - cryptoCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo, - ) { - val cryptoCurrency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return - val coinStatus = getNetworkCoinStatusUseCase( - userWalletId = userWallet.walletId, - networkId = cryptoCurrency.network.id, - derivationPath = cryptoCurrency.network.derivationPath, - isSingleWalletWithTokens = false, - ) - .firstOrNull() - ?.getOrNull() - ?: return - - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = cryptoCurrency, - tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, - coinFiatRate = coinStatus.value.fiatRate, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 3b15d7ee6c..7abe409e3a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -21,7 +21,6 @@ import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.utils.lceError import com.tangem.domain.demo.IsDemoCardUseCase @@ -32,13 +31,14 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.tokens.* +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase +import com.tangem.domain.tokens.RemoveCurrencyUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -53,9 +53,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import java.math.BigDecimal import javax.inject.Inject @@ -108,8 +106,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getExploreUrlUseCase: GetExploreUrlUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -136,63 +132,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( if (handleUnavailabilityReason(unavailabilityReason)) return stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, cryptoCurrencyStatus).getOrNull() - when (val currency = cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> sendCoin(cryptoCurrencyStatus, userWallet, maybeFeeCurrencyStatus) - is CryptoCurrency.Token -> sendToken( - cryptoCurrency = currency, - cryptoCurrencyStatus = cryptoCurrencyStatus.value, - feeCurrencyStatus = maybeFeeCurrencyStatus, - userWallet = userWallet, - ) - } - } - } - - private fun sendCoin( - cryptoCurrencyStatus: CryptoCurrencyStatus, - userWallet: UserWallet, - feeCurrencyStatus: CryptoCurrencyStatus?, - ) { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - ), + val route = AppRoute.Send( + currency = cryptoCurrencyStatus.currency, + userWalletId = userWallet.walletId, ) - } - private fun sendToken( - cryptoCurrency: CryptoCurrency.Token, - cryptoCurrencyStatus: CryptoCurrencyStatus.Value, - feeCurrencyStatus: CryptoCurrencyStatus?, - userWallet: UserWallet, - ) { - viewModelScope.launch(dispatchers.main) { - getNetworkCoinStatusUseCase( - userWalletId = userWallet.walletId, - networkId = cryptoCurrency.network.id, - derivationPath = cryptoCurrency.network.derivationPath, - isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .take(count = 1) - .collectLatest { - it.onRight { coinStatus -> - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = cryptoCurrency, - tokenFiatRate = cryptoCurrencyStatus.fiatRate, - coinFiatRate = coinStatus.value.fiatRate, - feeCurrencyStatus = feeCurrencyStatus, - ), - ) - } - } - } + appRouter.push(route) } override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { From 6184ce80d56221352ad9c8d01c9234d0510d35e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 19:55:41 +0500 Subject: [PATCH 24/40] Updated on 2026-08-14 --- .../domain/onramp/GetOnrampStatusUseCase.kt | 4 +- .../model/OnrampSuccessComponentModel.kt | 45 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt index a79f332c23..20effbbeeb 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt @@ -11,9 +11,9 @@ class GetOnrampStatusUseCase( private val errorResolver: OnrampErrorResolver, ) { - suspend operator fun invoke(externalTxId: String): Either { + suspend operator fun invoke(txId: String): Either { return Either.catch { - onrampRepository.getStatus(externalTxId) + onrampRepository.getStatus(txId) }.mapLeft(errorResolver::resolve) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt index 935b18a42f..514a00dc19 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -1,19 +1,28 @@ package com.tangem.features.onramp.success.model +import androidx.compose.ui.res.stringResource import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler 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.decompose.ui.UiMessageSender import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.message.ContentMessage import com.tangem.domain.onramp.GetOnrampStatusUseCase import com.tangem.domain.onramp.GetOnrampTransactionUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.success.entity.OnrampSuccessClickIntents import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM import com.tangem.features.onramp.success.entity.conterter.SetOnrampSuccessContentConverter @@ -36,6 +45,8 @@ internal class OnrampSuccessComponentModel @Inject constructor( private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val messageSender: UiMessageSender, + private val router: Router, paramsContainer: ParamsContainer, ) : Model(), OnrampSuccessClickIntents { @@ -58,8 +69,9 @@ internal class OnrampSuccessComponentModel @Inject constructor( modelScope.launch { getOnrampTransactionUseCase(externalTxId = params.externalTxId) .fold( - ifLeft = { - Timber.e(it.toString()) + ifLeft = { error -> + Timber.e(error.toString()) + showErrorAlert(error) }, ifRight = { transaction -> loadTransactionStatus(transaction) @@ -74,7 +86,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( transaction.toCurrencyId, ).getOrElse { error("Crypto currency not found") } - getOnrampStatusUseCase(externalTxId = params.externalTxId) + getOnrampStatusUseCase(txId = transaction.txId) .fold( ifLeft = { error -> analyticsEventHandler.sendOnrampErrorEvent( @@ -84,6 +96,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( paymentMethod = transaction.paymentMethod, ) Timber.e(error.toString()) + showErrorAlert(error) }, ifRight = { status -> analyticsEventHandler.send( @@ -112,6 +125,32 @@ internal class OnrampSuccessComponentModel @Inject constructor( ) } + private fun showErrorAlert(error: OnrampError) { + val contentMessage = ContentMessage { onDismiss -> + val errorCode = (error as? OnrampError.DataError)?.code + val message = if (errorCode.isNullOrBlank()) { + stringResource(R.string.common_unknown_error) + } else { + stringResource(R.string.express_error_code, wrappedList(errorCode)) + } + BasicDialog( + message = message, + confirmButton = DialogButtonUM( + title = stringResource(id = R.string.common_ok), + onClick = { + router.pop() + onDismiss() + }, + ), + onDismissDialog = { + router.pop() + onDismiss() + }, + ) + } + messageSender.send(contentMessage) + } + private fun removeTransactionIfTerminalStatus( cryptoCurrency: CryptoCurrency, providerName: String, From 0c9bb4e4ea0acec34a1ea8067e75072072001274 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 18:13:59 +0300 Subject: [PATCH 25/40] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 + core/res/src/main/res/values-es/strings.xml | 84 +++++++++++-------- core/res/src/main/res/values-fr/strings.xml | 2 + core/res/src/main/res/values-ja/strings.xml | 2 + core/res/src/main/res/values-ru/strings.xml | 2 + .../src/main/res/values-uk-rUA/strings.xml | 2 + core/res/src/main/res/values/strings.xml | 2 + .../UpdateCurrencyItemsTransformer.kt | 7 +- .../model/OnrampSelectCurrencyModel.kt | 8 +- 9 files changed, 69 insertions(+), 42 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 3cc2368a82..aa37b93bdc 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -583,6 +583,8 @@ Der Kauf von Kryptowährungen bei Tangem wird von Drittanbietern zu deren Bedingungen durchgeführt. Suche nach Land Nicht verfügbar + Andere Währungen + Beliebte Fiats Suche nach Währung Durch die Nutzung der Onramp-Funktionalität stimmst Du den %1$s und %2$s des Anbieters zu. Der Kaufbetrag sollte nicht höher sein als %s diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 683983950c..1bd3de114c 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -6,10 +6,15 @@ Esto puede tardar unos segundos. Inténtelo de nuevo más tarde. Los datos aún no se han cargado. La acción no está disponible actualmente. Inténtalo de nuevo más tarde o actualiza los datos deslizando el dedo hacia abajo en la pantalla. + No disponible Elige el token - ¿No encuentras el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio + ¿No encuentra el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio No hay tokens disponibles para intercambiar con el token seleccionado. Por favor elige otro. No hay par disponible + Para utilizar la función de intercambio, su billetera debe contener al menos 2 tokens + Añada tokens + Sólo tienes 1 token añadido a tu portafolio. Para utilizar la función de intercambio, necesita añadir al menos 2 tokens + Añada tokens Seleccione el token que desea recibir Seleccione el token que desea intercambiar Elige red @@ -25,7 +30,7 @@ El seleccionado no admite la red %1$s Para activar el %1$s cifrado criptográfico de la blockchain, necesitarás reiniciar la billetera a los ajustes de fábrica. Por favor, retire sus fondos antes de hacerlo para asegurarte de no perderlos, y luego complete el proceso de reinicio. El acceso a la billetera actual no será posible después del reinicio. Esta tarjeta no admite %1$s tokens de red debido a una limitación del firmware. - ¿Tiene dificultades para escanear su tarjeta? + ¿Tiene dificultades para escanear su tarjeta/anillo? Esta tarjeta no está diseñada para funcionar con Tangem Tarifa por defecto Habilite las Tarifas predeterminadas para establecer automáticamente las tarifas de transacción y omitir la página de Tarifas al enviar fondos. Siempre puede volver a esta página si es necesario. @@ -65,10 +70,10 @@ El código de acceso se cambiará solo en esta tarjeta o este anillo Todas las tarjetas de la billetera seleccionada se han restablecido a la configuración de fábrica. Ahora puedes crear una nueva billetera. Reinicio completado - ¿Quiere restablecer la siguiente tarjeta en esta billetera? + ¿Quiere restablecer el siguiente dispositivo en esta billetera? Reinicio de tarjeta Recomendamos completar el proceso de reinicio para todas las tarjetas en esta billetera. - No ha reseteado todas sus tarjetas + No ha reseteado todas sus dispositivos Tangem Restablecer a ajustes de fábrica Modo de seguridad Ajustes del dispositivo @@ -266,6 +271,9 @@ El monto fue reembolsado en %1$s (red %2$s) Visite el sitio web del proveedor para la verificación Verificación KYC requerida por el proveedor + Comprado + Comprando + Comprando... Cancelado Confirmado Esperando confirmación @@ -305,15 +313,15 @@ No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado - Convierta una de sus monedas a otra + Convierta una de sus activos de su portafolio por este token La siguiente información es opcional. Puede borrarla si no quiere compartirla. Cuéntenos qué funciones echa de menos y trataremos de ayudarle. - Por favor, díganos qué tarjeta tiene + Por favor, díganos qué tarjeta o anillo tiene Hola equipo de soporte, Por favor, cuéntenos más sobre tu problema. Cada pequeño detalle puede ayudar. Billetera previamente activada Mis recomendaciones - No se puede escanear una tarjeta + No se puede escanear una tarjeta/anillo Comentarios Comentarios en Tangem No se puede completar una transacción @@ -329,14 +337,14 @@ Ordenar Escanee Esta información fue generada con IA.\nPulse aquí si encuentra algún error. - Toque para cambiar la contraseña + Para cambiar el código de acceso coloque la tarjeta o el anillo como se muestra arriba y no lo retire hasta el fin de la operación Toque para cambiar la contraseña - Para crear la billetera, toque la tarjeta como se muestra arriba y no la retire hasta el final de la operación + Para crear la billetera, coloque la tarjeta o el anillo como se muestra arriba y no la retire hasta el fin de la operación Para crear la billetera, ponga el anillo como se muestra arriba y manténgala hasta que termine la operación. Toque la tarjeta no. %s de la billetera Coloque para escanear Toque para firmar - Coloque la tarjeta + Coloque la tarjeta o el anillo Ha actualizado la biometría, escanee su tarjeta o anillo para entrar Su saldo debe ser mayor que el valor de la tarifa para hacer una transferencia Saldo insuficiente @@ -379,7 +387,7 @@ Este activo no está disponible en la billetera Este activo no está disponible para esta billetera Añadir al portafolio - Agregue un token + Añadir Redes disponibles Mi portafolio Mercado @@ -391,7 +399,7 @@ Acciones rápidas Busque en el mercado Resultado - Ver tokens con una capitalización de mercado de menos de $100,000 + Ver tokens con una capitalización de mercado inferior a 100,000$ Mostrar tokens Sin resultado Seleccione una red @@ -478,9 +486,9 @@ NFC no está disponible en su dispositivo Deba configurar un único código de acceso para proteger todss sus dispositivos. Proteger - Puede configurar un código de acceso individual en cada tarjeta más tarde + Puede configurar un código de acceso individual en cada tarjeta más adelante Personalizar - El código de acceso se puede recuperar con una tarjeta o un anillo vinculada(o). No guarde todas las tarjetas en un mismo lugar. + El código de acceso se puede recuperar con una tarjeta o un anillo vinculada(o). No guarde todos los dispositivos en el mismo lugar. Restaurar Elige cualquier palabra, frase o número que quieras como su código de acceso Crear un código de acceso @@ -491,12 +499,12 @@ Por favor repita la operación. La tarjeta se restablecerá a la configuración de fábrica. Error de activación Agregar tokens - Ha agregado una tarjeta de backup. Cuando el proceso de backup finalice, no podrá añadir más tarjetas de backup. Si tiene otra tarjeta, agréguela al backup. ¿Quiere continuar el proceso de backup? + Ha agregado una tarjeta/anillo de backup. Cuando el proceso de backup finalice, no podrá añadir más dispositivos de backup. Si tiene otra tarjeta o anillo, agréguela al backup. ¿Quiere continuar el proceso de backup? El proceso de backup está parcialmente completo. No puede salir ahora. La frase de contraseña es una característica de seguridad avanzada utilizada por las billeteras criptográficas. Agrega una palabra o frase adicional de su elección a su frase de recuperación ya existente para desbloquear un conjunto completamente nuevo de direcciones. Agregar una tarjeta o un anillo de backup Escanee la tarjeta - Escanee la tarjeta no. %d + Escanee la tarjeta núm. %d Hacer backup ahora Escanee la tarjeta principal Escanee el anillo @@ -506,7 +514,7 @@ Escanee la tarjeta principal Finalizar más tarde ¿Cómo funciona? - Vamos a generar todas las claves en su tarjeta y crear una billetera segura + Vamos a generar todas las claves en su tarjeta o anillos y crear una billetera segura Crear una billetera Crear una billetera Otras opciones @@ -539,23 +547,23 @@ Legado Para comprobar si has escrito correctamente su seed phrase, por favor introduce las palabras 2ª, 7ª y 11ª Bien, vamos a comprobar - Para iniciar el proceso de backup, agregue hasta dos tarjetas de backup. - Puede agregar una tarjeta más o finalizar el proceso de backup + Para iniciar el proceso de backup, agregue hasta dos tarjetas/anillos de backup. + Puede agregar una tarjeta/anillo más o finalizar el proceso de backup Prepare su tarjeta de backup con el no.%s Escanee la tarjeta principal o el anillo para iniciar el proceso de backup. Prepare la tarjeta principal con el número %s Prepare el anillo y presione el botón de escaneo a continuación - Su tarjeta billetera está configurada y lista para usar. - Número máximo de tarjetas añadido. Finalice el proceso de backup. - Activando tarjeta + Su billetera está configurada y lista para usar. + Número máximo de dispositivos añadido. Finalice el proceso de backup. + Activando billetera Tarjeta de backup Tarjeta de backup no.%d Anillo de backup Ningunos dispositivos de backup Notificaciones - Una tarjeta de backup agregada + Un dispositivo de backup agregado Prepare su tarjeta - Dos tarjetas de backup agregadas + Dos dispositivos de backup agregados Para empezar, simplemente recargue la billetera con cualquier cantidad Para empezar, simplemente recargue la billetera con más de %1$s %2$s Comprar cripto @@ -563,7 +571,7 @@ Activar una billetera El proceso de emparejamiento está parcialmente completo. No puede salir ahora. Si el proceso de creación del wallet se interrumpe de alguna manera, tendrá que empezar de nuevo - Puede hacer backup de sus claves en hasta dos tarjetas Tangem Wallet en blanco. + Puede hacer backup de sus claves en hasta dos tarjetas o anillos Tangem no activadas. El código de acceso se puede restaurar con una de las tarjetas de backup. Todas las tarjetas de backup se pueden usar como funcionales con las claves idénticas. Podrá establecer un código de acceso para proteger sus billeteras. @@ -572,8 +580,11 @@ Tarjetas idénticas Código de acceso Disponible con %s + La compra de criptomonedas en Tangem se lleva a cabo utilizando proveedores externos usando sus propios términos. Buscar por país Indisponible + Otras monedas + Monedas Fiats Populares Buscar por moneda Al utilizar la funcionalidad onramp, acepta %1$s y %2$s del proveedor. El monto de la compra no debe ser mayor a %s @@ -604,7 +615,8 @@ %1$s (%2$s) en la red %3$s El envío de cualquier otra moneda supondrá una pérdida irreversible. Envíe solo %s a esta dirección. Enviar cualquier otra moneda resultará en su pérdida irreversible. - Transfiera fondos desde otra billetera o intercambio + Envíe solo %1$s en la red %2$s + Transfiera fondos desde otra billetera o exchange Participar Error al cargar la información sobre el programa de referidos. Por favor, inténtelo de nuevo más tarde. Error al cargar la información sobre el programa de referidos. Código de error: %s. Por favor, inténtelo de nuevo más tarde. @@ -806,17 +818,17 @@ Revotar Auto Manual - Bloquear - Día + Por Bloque + Diario Cada %s Cada día - Cada %s + Cada %s días Cada - Época + Por Época Era - Hora - Mes - Semana + Cada Hora + Mensual + Semanal Recompensas El stake está bloqueado Hacer más staking @@ -843,7 +855,7 @@ Votar Retirar Sus stakes - Almacene sus activos crypto de forma segura manteniendo las claves privadas contenidas en su tarjeta + Almacene sus criptomonedas de forma segura manteniendo las claves privadas almacenadas en su tarjeta Billetera de hardware revolucionaria Hasta 3 tarjetas físicas o anillos por billetera Backup ultra seguro @@ -857,7 +869,7 @@ La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap. Intercambie más tokens a mejores tasas directamente en su billetera. ¡Nuevo proveedor de intercambio disponible! - El monto incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. + La cantidad incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. El monto incluye:\n• tarifas del proveedor de servicios\n• tarifas de red por enviar %1$s desde el intercambio a la dirección del usuario. \n\nEl slippage del proveedor puede alcanzar el %2$s El importe incluye los honorarios del proveedor de servicios. El importe incluye las tarifas del proveedor de servicios. \n\nEl slippage del proveedor puede alcanzar el %s @@ -920,7 +932,7 @@ Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra Esta que tiene en sus manos y la otra con el número %s.\n\nAmbas tarjetas pueden usarse para extraer fondos de esta billetera. Una billetera. Dos tarjetas. - Escanee la tarjeta no. %s + Escanee la tarjeta núm. %s Creando la billetera Escanear tarjeta gemela no. %s Preparando la tarjeta diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 55d428f3d3..4972952622 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -582,6 +582,8 @@ Disponible avec %s Recherche par pays Indisponible + Autres devises + Devises Fiats Populaires Recherche par devise En utilisant la fonctionnalité onramp, vous acceptez %1$s et %2$s du fournisseur Le montant de l\'achat ne doit pas dépasser %s diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index bd018bcf92..66cd763ddb 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -575,6 +575,8 @@ Tangemでの暗号通貨の買付は、サードパーティプロバイダーの条件に基づいて行われます。 国で検索 利用不可 + その他の通貨 + 人気の法定通貨 通貨で検索 オンランプ機能を使用することにより、プロバイダの%1$sおよび%2$sに同意するものとします 買付金額は%s以下にしてください diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f6655e8627..4bd80de49f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -597,6 +597,8 @@ Tangem предоставляет доступ к покупке через сторонних провайдеров в соответствии с их правилами Поиск по стране Недоступно + Другие валюты + Популярные фиаты Поиск по валюте Пользуясь сервисом покупки, вы соглашаетесь с %1$s и %2$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 e93c5799e5..9b615a149f 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -599,6 +599,8 @@ Tangem забезпечує доступ к покупці через сторонніх провайдерів згідно з їхніми умовами Пошук за країною Недоступно + Інші валюти + Популярні фіати Пошук по валюті Використовуючи сервіс покупки, ви погоджуєтесь з %1$s та %2$s Сума покупки не може бути більше ніж %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 12d15e5487..ede0aab5f3 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -583,6 +583,8 @@ Buying crypto in Tangem is powered by third-party providers on their terms. Search by country Unavailable + Other currencies + Popular Fiats Search by currency By using onramp functionality, you agree with provider’s %1$s and %2$s The purchase amount should be no more than %s diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt index e5e3328194..00e776e170 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt @@ -1,10 +1,11 @@ package com.tangem.features.onramp.selectcurrency.entity.transformer import arrow.core.Either -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.model.OnrampCurrencies import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.model.error.OnrampError +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState @@ -28,7 +29,7 @@ internal class UpdateCurrencyItemsTransformer( if (populars.isNotEmpty()) { add( CurrenciesSection( - title = stringReference("Popular fiats"), + title = resourceReference(R.string.onramp_currency_popular), items = populars.map(::convertCurrencyToUiModel).toImmutableList(), ), ) @@ -36,7 +37,7 @@ internal class UpdateCurrencyItemsTransformer( if (others.isNotEmpty()) { add( CurrenciesSection( - title = stringReference("Other currencies"), + title = resourceReference(R.string.onramp_currency_other), items = others.map(::convertCurrencyToUiModel).toImmutableList(), ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index 127d38cb94..a297ae455a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -6,7 +6,6 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.onramp.FetchOnrampCurrenciesUseCase import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase @@ -136,8 +135,11 @@ internal class OnrampSelectCurrencyModel @Inject constructor( private companion object { val loadingSections = listOf( - CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")), - CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")), + CurrenciesSection( + resourceReference(R.string.onramp_currency_popular), + items = createLoadingItems("popular"), + ), + CurrenciesSection(resourceReference(R.string.onramp_currency_other), items = createLoadingItems("other")), ).toImmutableList() private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList = From ff42bcfcb52e677fb06b5198c529e35e1ff29c8a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 22:17:32 +0500 Subject: [PATCH 26/40] Updated on 2026-08-14 --- .../data/onramp/DefaultOnrampRepository.kt | 5 + .../tangem/domain/onramp/model/OnrampQuote.kt | 4 + .../onramp/main/entity/OnrampLastUpdate.kt | 8 ++ .../main/entity/factory/OnrampStateFactory.kt | 13 ++- .../amount/OnrampAmountStateFactory.kt | 34 +++++- .../main/model/OnrampMainComponentModel.kt | 103 +++++++++++------- 6 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 25a92979ef..578611fb04 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -249,6 +249,7 @@ internal class DefaultOnrampRepository( maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency), paymentMethod = paymentMethod, provider = provider, + countryCode = response.countryCode, ) }, onError = { error -> @@ -257,6 +258,7 @@ internal class DefaultOnrampRepository( paymentMethod = paymentMethod, provider = provider, fromOnrampAmount = fromOnrampAmount, + countryCode = country.code, ) }, ) @@ -454,6 +456,7 @@ internal class DefaultOnrampRepository( paymentMethod: OnrampPaymentMethod, provider: OnrampProvider, fromOnrampAmount: OnrampAmount, + countryCode: String, ) = if (error is ApiResponseError.HttpException) { val onrampError = onrampErrorConverter.convert(value = error.errorBody.orEmpty()) if (onrampError is OnrampError.AmountError) { @@ -462,6 +465,7 @@ internal class DefaultOnrampRepository( provider = provider, fromAmount = fromOnrampAmount, error = onrampError, + countryCode = countryCode, ) } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") @@ -470,6 +474,7 @@ internal class DefaultOnrampRepository( provider = provider, fromAmount = fromOnrampAmount, error = onrampError, + countryCode = countryCode, ) } } else { diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index c36638db32..dde5f19064 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -7,11 +7,13 @@ sealed class OnrampQuote { abstract val paymentMethod: OnrampPaymentMethod abstract val provider: OnrampProvider abstract val fromAmount: OnrampAmount + abstract val countryCode: String data class Data( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val toAmount: OnrampAmount, val minFromAmount: OnrampAmount, val maxFromAmount: OnrampAmount, @@ -21,6 +23,7 @@ sealed class OnrampQuote { override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val error: OnrampError.AmountError, ) : OnrampQuote() @@ -28,6 +31,7 @@ sealed class OnrampQuote { override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val error: OnrampError, ) : OnrampQuote() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt new file mode 100644 index 0000000000..bc6113303c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt @@ -0,0 +1,8 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.domain.onramp.model.OnrampAmount + +data class OnrampLastUpdate( + val lastAmount: OnrampAmount, + val lastCountryString: String, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index 136405af33..18a7bb1727 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -49,8 +49,11 @@ internal class OnrampStateFactory( fun getOnrampErrorState(onrampError: OnrampError): OnrampMainComponentUM { return when (onrampError) { OnrampError.PairsNotFound -> getNoPairsErrorState() - is OnrampError.DataError -> getErrorState(onrampError.code) - is OnrampError.DomainError -> getErrorState() + is OnrampError.DataError -> getErrorState( + errorCode = onrampError.code, + onRefresh = onrampIntents::onRefresh, + ) + is OnrampError.DomainError -> getErrorState(onRefresh = onrampIntents::onRefresh) is OnrampError.AmountError.TooBigError, is OnrampError.AmountError.TooSmallError, OnrampError.RedirectError.VerificationFailed, @@ -74,7 +77,7 @@ internal class OnrampStateFactory( ) } - fun getErrorState(errorCode: String? = null): OnrampMainComponentUM { + fun getErrorState(errorCode: String? = null, onRefresh: () -> Unit): OnrampMainComponentUM { val state = currentStateProvider() val endButton = state.topBarConfig.endButtonUM.copy(enabled = true) @@ -89,13 +92,13 @@ internal class OnrampStateFactory( providerBlockState = OnrampProviderBlockUM.Empty, errorNotification = NotificationUM.Warning.OnrampErrorNotification( errorCode = errorCode, - onRefresh = onrampIntents::onRefresh, + onRefresh = onRefresh, ), ) is OnrampMainComponentUM.InitialLoading -> state.copy( errorNotification = NotificationUM.Warning.OnrampErrorNotification( errorCode = errorCode, - onRefresh = onrampIntents::onRefresh, + onRefresh = onRefresh, ), ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 93b66cbc95..2a443efd36 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -14,15 +14,18 @@ import com.tangem.domain.onramp.model.OnrampProviderWithQuote import com.tangem.domain.onramp.model.OnrampQuote import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.tokens.model.AmountType +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.* import com.tangem.features.onramp.providers.entity.SelectProviderResult import com.tangem.utils.Provider +import com.tangem.utils.extensions.isSingleItem internal class OnrampAmountStateFactory( private val currentStateProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, private val onrampIntents: OnrampIntents, + private val cryptoCurrency: CryptoCurrency, ) { private val onrampAmountFieldChangeConverter = OnrampAmountFieldChangeConverter( @@ -62,11 +65,12 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading), + providerBlockState = OnrampProviderBlockUM.Loading, buyButtonConfig = currentState.buyButtonConfig.copy(enabled = false), ) } - fun getAmountSecondaryUpdatedState(quote: OnrampQuote, isBestRate: Boolean): OnrampMainComponentUM { + fun getAmountSecondaryUpdatedState(quote: OnrampQuote): OnrampMainComponentUM { val currentState = currentStateProvider() if (currentState !is OnrampMainComponentUM.Content) return currentState @@ -75,9 +79,9 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy( + amountFieldModel = amountState.amountFieldModel.copy(isError = false), secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel, ), - providerBlockState = quote.toProviderBlockState(isBestRate), buyButtonConfig = currentState.buyButtonConfig.copy( enabled = quote is OnrampQuote.Data, onClick = { @@ -96,6 +100,32 @@ internal class OnrampAmountStateFactory( ) } + fun getUpdatedProviderState(selectedQuote: OnrampQuote, quotes: List): OnrampMainComponentUM { + val currentState = currentStateProvider() + if (currentState !is OnrampMainComponentUM.Content) return currentState + + analyticsEventHandler.send( + OnrampAnalyticsEvent.ProviderCalculated( + providerName = selectedQuote.provider.info.name, + tokenSymbol = cryptoCurrency.symbol, + paymentMethod = selectedQuote.paymentMethod.name, + ), + ) + + val bestProvider = selectedQuote as? OnrampQuote.Data + val isMultipleQuotes = !quotes.isSingleItem() + val isOtherQuotesHasData = quotes + .filter { it.paymentMethod == selectedQuote.paymentMethod } + .filterNot { it == bestProvider } + .any { it is OnrampQuote.Data } + + val isBestProvider = selectedQuote == bestProvider && isMultipleQuotes && isOtherQuotesHasData + + return currentState.copy( + providerBlockState = selectedQuote.toProviderBlockState(isBestProvider), + ) + } + fun getAmountSecondaryUpdatedState( providerResult: SelectProviderResult, isBestRate: Boolean, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 865ec89ec6..f53b13e685 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -25,10 +25,7 @@ import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.OnrampMainComponent -import com.tangem.features.onramp.main.entity.OnrampIntents -import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig -import com.tangem.features.onramp.main.entity.OnrampMainComponentUM -import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM +import com.tangem.features.onramp.main.entity.* import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory import com.tangem.features.onramp.providers.entity.SelectProviderResult @@ -38,15 +35,13 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.PeriodicTask import com.tangem.utils.coroutines.SingleTaskScheduler -import com.tangem.utils.extensions.isSingleItem import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber -import java.math.BigDecimal import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class OnrampMainComponentModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, @@ -75,6 +70,7 @@ internal class OnrampMainComponentModel @Inject constructor( currentStateProvider = Provider { _state.value }, analyticsEventHandler = analyticsEventHandler, onrampIntents = this, + cryptoCurrency = params.cryptoCurrency, ) private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } private val _state: MutableStateFlow = MutableStateFlow( @@ -87,7 +83,7 @@ internal class OnrampMainComponentModel @Inject constructor( val state: StateFlow get() = _state.asStateFlow() val bottomSheetNavigation: SlotNavigation = SlotNavigation() - private val lastAmount = mutableStateOf(BigDecimal.ZERO) + private val lastUpdateState = mutableStateOf(null) init { sendScreenOpenAnalytics() @@ -161,7 +157,11 @@ internal class OnrampMainComponentModel @Inject constructor( } private suspend fun updatePairsAndQuotes() { - _state.update { amountStateFactory.getAmountSecondaryLoadingState() } + val state = state.value as? OnrampMainComponentUM.Content + + if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) { + _state.update { amountStateFactory.getAmountSecondaryLoadingState() } + } fetchPairsUseCase.invoke(params.cryptoCurrency).fold( ifLeft = ::handleOnrampError, ifRight = { _state.update { amountStateFactory.getAmountSecondaryResetState() } }, @@ -200,7 +200,6 @@ internal class OnrampMainComponentModel @Inject constructor( private fun subscribeToQuotesUpdate() { getOnrampQuotesUseCase.invoke() - .distinctUntilChanged() .conflate() .onEach { maybeQuotes -> maybeQuotes.fold( @@ -282,42 +281,67 @@ internal class OnrampMainComponentModel @Inject constructor( private fun handleQuoteResult(quotes: List) { sendOnrampQuotesErrorAnalytic(quotes) - val quote = quotes.firstOrNull { it !is OnrampQuote.Error } + val quote = selectOrUpdateQuote(quotes) if (quote == null) { - _state.update { stateFactory.getErrorState() } - lastAmount.value = BigDecimal.ZERO + _state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) } + lastUpdateState.value = null return } - val bestProvider = quote as? OnrampQuote.Data - val isMultipleQuotes = !quotes.isSingleItem() - val isOtherQuotesHasData = quotes - .filter { it.paymentMethod == quote.paymentMethod } - .filterNot { it == bestProvider } - .any { it is OnrampQuote.Data } - val hasBestProvider = isMultipleQuotes && isOtherQuotesHasData + if (checkLastInputState(quote)) { + lastUpdateState.value = OnrampLastUpdate( + quote.fromAmount, + quote.countryCode, + ) - val isBestProvider = quote == bestProvider && hasBestProvider - - if (lastAmount.value != quote.fromAmount.value) { - lastAmount.value = quote.fromAmount.value - if (quote is OnrampQuote.Data) { - analyticsEventHandler.send( - OnrampAnalyticsEvent.ProviderCalculated( - providerName = quote.provider.info.name, - tokenSymbol = params.cryptoCurrency.symbol, - paymentMethod = quote.paymentMethod.name, - ), - ) - } _state.update { - amountStateFactory.getAmountSecondaryUpdatedState( - quote = quote, - isBestRate = isBestProvider, - ) + amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes) } } + _state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote = quote) } + } + + /** + * !!! Important quote selection logic !!! + * Selects or updated quote based on input data (amount, country, currency). + * If input data has changed select new best quote, otherwise last selected quote. + * If last selected quote on same input data is in an error state, select next best quote + * If new best quote or next best quote does not exist (i.e. Error state) select nothing. + */ + private fun selectOrUpdateQuote(quotes: List): OnrampQuote? { + val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error } + + // Check if amount, country or currency has changed + return if (checkLastInputState(quoteToCheck)) { + quoteToCheck + } else { + val state = state.value as? OnrampMainComponentUM.Content + val providerState = state?.providerBlockState as? OnrampProviderBlockUM.Content + + // Get current selected quote to update + val lastSelectedQuote = quotes.firstOrNull { + it.provider.id == providerState?.providerId && + it.paymentMethod.id == providerState.paymentMethod.id + } + + // Check if selected updated quote is not error + if (lastSelectedQuote is OnrampQuote.Error) { + quoteToCheck + } else { + lastSelectedQuote + } + } + } + + private fun onRetryQuotes() { + _state.update { + (it as? OnrampMainComponentUM.Content)?.copy( + errorNotification = null, + providerBlockState = OnrampProviderBlockUM.Loading, + ) ?: it + } + startLoadingQuotes() } private fun showDemoWarning() { @@ -380,6 +404,11 @@ internal class OnrampMainComponentModel @Inject constructor( } } + private fun checkLastInputState(quote: OnrampQuote?): Boolean { + return lastUpdateState.value?.lastAmount != quote?.fromAmount || + lastUpdateState.value?.lastCountryString != quote?.countryCode + } + private companion object { const val UPDATE_DELAY = 10_000L } From 0eed7867a88308e46b99fab60f92805619eef568 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Dec 2024 22:42:52 +0500 Subject: [PATCH 27/40] Updated on 2026-08-14 --- .../onramp/confirmresidency/model/ConfirmResidencyModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index f622253ea1..270c3e6f73 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -52,8 +52,10 @@ internal class ConfirmResidencyModel @Inject constructor( ConfirmResidencyUM.ActionButtonConfig( onClick = { analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name)) - modelScope.launch { saveDefaultCountryUseCase.invoke(country) } - params.onDismiss() + modelScope.launch { + saveDefaultCountryUseCase.invoke(country) + params.onDismiss() + } }, text = resourceReference(R.string.common_confirm), ) From cff20ef5156c88000212e95b6118f0e693dfdbf7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Dec 2024 15:26:39 +0300 Subject: [PATCH 28/40] Updated on 2026-08-14 --- .../onramp/selectcountry/ui/SelectCountryBottomSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt index 6fbf4c8d16..b74c50c6b1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt @@ -185,7 +185,7 @@ private fun UnavailableCountryItem(state: CountryItemState.WithContent.Unavailab color = TangemTheme.colors.text.disabled, ) Text( - text = "Unavailable", + text = stringResource(R.string.onramp_country_unavailable), maxLines = 1, style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, From 3c602ba584ff49991570c9f33dce20fff9297898 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Dec 2024 15:35:25 +0300 Subject: [PATCH 29/40] Updated on 2026-08-14 --- .../src/main/assets/configs/excluded_blockchains_config.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json index 5a48cc92c0..ae77ec7b7c 100644 --- a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json +++ b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json @@ -10,9 +10,5 @@ { "name": "NEXA/test", "version": "undefined" - }, - { - "name": "clore-ai", - "version": "undefined" } ] \ No newline at end of file From 29df79b1c2d954a955f50547fd3727e9d0aafa1e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Dec 2024 16:42:36 +0400 Subject: [PATCH 30/40] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 2 +- .../main/java/com/tangem/tap/MainActivity.kt | 36 ++++++++++++++++--- .../models/event/TechAnalyticsEvent.kt | 26 ++++++++++++++ .../disclaimer/impl/ui/DisclaimerScreen.kt | 21 +++++++---- 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/TechAnalyticsEvent.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8981d0a0b1..fbb777f36c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ - + diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index e0a2bf62d7..469ab5c092 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -32,6 +32,8 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.event.TechAnalyticsEvent +import com.tangem.core.analytics.models.event.TechAnalyticsEvent.WindowObscured.ObscuredState import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.deeplink.DeepLinksRegistry @@ -222,6 +224,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() + private var isWindowPartiallyObscuredAlreadySent: Boolean = false + private var isWindowFullyObscuredAlreadySent: Boolean = false + override fun onCreate(savedInstanceState: Bundle?) { // We need to call it before onCreate to prevent unnecessary activity recreation installAppTheme() @@ -246,9 +251,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - window.setHideOverlayWindows(true) - } + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // window.setHideOverlayWindows(true) + // } splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenShown } @@ -530,10 +535,31 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac false } + if (isPartiallyObscured) { + Timber.d("Window is partially obscured") + + if (!isWindowPartiallyObscuredAlreadySent) { + analyticsEventsHandler.send( + event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.PARTIALLY), + ) + + isWindowPartiallyObscuredAlreadySent = true + } + } + val isFullyObscured = event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 - if (isPartiallyObscured || isFullyObscured) { - Timber.e("Window is partially or fully obscured") + if (isFullyObscured) { + Timber.d("Window is partially or fully obscured") + + if (!isWindowFullyObscuredAlreadySent) { + analyticsEventsHandler.send( + event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.FULLY), + ) + + isWindowFullyObscuredAlreadySent = true + } + return false } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/TechAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/TechAnalyticsEvent.kt new file mode 100644 index 0000000000..7d7460a633 --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/TechAnalyticsEvent.kt @@ -0,0 +1,26 @@ +package com.tangem.core.analytics.models.event + +import com.tangem.core.analytics.models.AnalyticsEvent + +/** + * Tech analytics event + * + * @param event event name + * @param params params + */ +sealed class TechAnalyticsEvent( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = "Tech", event = event, params = params) { + + class WindowObscured(state: ObscuredState) : TechAnalyticsEvent( + event = "Window Obscured", + params = mapOf("State" to state.name), + ) { + + enum class ObscuredState { + PARTIALLY, + FULLY, + } + } +} \ No newline at end of file diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt index 1fcc978ace..7ffb017cfa 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt @@ -47,11 +47,16 @@ import java.nio.charset.StandardCharsets @Composable internal fun DisclaimerScreen(state: DisclaimerUM) { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - val bottomPadding = bottomBarHeight + TangemTheme.dimens.size16 - + val bottomPadding = if (state.isTosAccepted) { + bottomBarHeight + TangemTheme.dimens.size16 + } else { + bottomBarHeight + TangemTheme.dimens.size64 + } + val backgroundColor = TangemTheme.colors.background.primary + val (textColor, iconColor) = TangemTheme.colors.text.primary1 to TangemTheme.colors.icon.primary1 Box( modifier = Modifier - .background(TangemTheme.colors.background.primary) + .background(backgroundColor) .statusBarsPadding() .testTag(DISCLAIMER_SCREEN_CONTAINER), ) { @@ -67,6 +72,8 @@ internal fun DisclaimerScreen(state: DisclaimerUM) { onIconClicked = state.popBack, ).takeIf { state.isTosAccepted }, titleAlignment = Alignment.CenterHorizontally, + textColor = textColor, + iconTint = iconColor, ) DisclaimerContent(state.url) } @@ -74,7 +81,7 @@ internal fun DisclaimerScreen(state: DisclaimerUM) { if (!state.isTosAccepted) { BottomFade( modifier = Modifier.align(Alignment.BottomCenter), - backgroundColor = TangemTheme.colors.background.primary, + backgroundColor = backgroundColor, ) DisclaimerButton(state.onAccept) } else { @@ -85,6 +92,8 @@ internal fun DisclaimerScreen(state: DisclaimerUM) { @Composable private fun DisclaimerContent(url: String) { + val backgroundColor = TangemTheme.colors.background.primary + val webViewStateUrl = rememberWebViewState(url) val webViewStateData = rememberWebViewStateWithHTMLData( data = localTermsOfServices, @@ -119,12 +128,12 @@ private fun DisclaimerContent(url: String) { exit = fadeOut(), modifier = Modifier .fillMaxSize() - .background(TangemTheme.colors.background.primary), + .background(backgroundColor), ) { Box( modifier = Modifier .fillMaxSize() - .background(TangemTheme.colors.background.primary), + .background(backgroundColor), ) { CircularProgressIndicator( color = TangemTheme.colors.icon.informative, From 525149580598632bcfb131e44c3310e24da72270 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 21 Dec 2024 14:56:09 +0300 Subject: [PATCH 31/40] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index b15619d568..e980a73d6b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.18-874" +tangemBlockchainSdk = "release-app_5.18-887" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.18-413" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From d3d8dc8cc0e344d4107bd6ea95d22447663cdced Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Dec 2024 19:08:19 +0500 Subject: [PATCH 32/40] Updated on 2026-08-14 --- .../model/analytics/TokenScreenAnalyticsEvent.kt | 10 ++++++++++ .../analytics/TokenDetailsAnalyticsEvent.kt | 5 +++++ .../TokenDetailsNotificationsAnalyticsSender.kt | 4 +++- .../tokendetails/viewmodels/TokenDetailsViewModel.kt | 12 ++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) 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 d42f2f31b5..804f5d06c5 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 @@ -74,6 +74,16 @@ sealed class TokenScreenAnalyticsEvent( params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain), ) + class RevealTryAgain(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent( + event = "Button - Reveal Try Again", + params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain), + ) + + class RevealCancel(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent( + event = "Button - Reveal Cancel", + params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain), + ) + data class StakingClicked(val token: String) : TokenScreenAnalyticsEvent( event = "Staking Clicked", params = mapOf("Token" to token), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt index 8c5f6c268f..367ea0574d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt @@ -23,5 +23,10 @@ internal open class TokenDetailsAnalyticsEvent( event = "Not Enough Fee", params = currency.toAnalyticsParams(), ) + + class Reveal(currency: CryptoCurrency) : Notice( + event = "Notice - Reveal Transaction", + params = currency.toAnalyticsParams(), + ) } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 836894e575..414b838b91 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -41,6 +41,9 @@ internal class TokenDetailsNotificationsAnalyticsSender( programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, source = AnalyticsParam.ScreensSources.Token, ) + is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal( + currency = cryptoCurrency, + ) is TokenDetailsNotification.NetworksUnreachable, is TokenDetailsNotification.ExistentialDeposit, is TokenDetailsNotification.NetworksNoAccount, @@ -48,7 +51,6 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.RentInfo, is TokenDetailsNotification.NetworkShutdown, is TokenDetailsNotification.HederaAssociateWarning, - is TokenDetailsNotification.KaspaIncompleteTransactionWarning, is TokenDetailsNotification.KoinosMana, is TokenDetailsNotification.MigrationMaticToPol, -> null diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 250f345a70..e2446726d6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -825,6 +825,12 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onRetryIncompleteTransactionClick() { + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.RevealTryAgain( + tokenSymbol = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) viewModelScope.launch { retryIncompleteTransactionUseCase( userWalletId = userWalletId, @@ -860,6 +866,12 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onDismissIncompleteTransactionClick() { + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.RevealCancel( + tokenSymbol = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) viewModelScope.launch { internalUiState.value = stateFactory.getStateWithDismissIncompleteTransactionConfirmDialog() } From e13cce2cf8f2ff5b3c9222fb9d61724cb85e01e5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Dec 2024 10:58:26 +0400 Subject: [PATCH 33/40] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 43 ++---------- .../tangem/tap/WindowObscurationObserver.kt | 68 +++++++++++++++++++ .../tangem/tap/features/main/MainViewModel.kt | 1 + 3 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/WindowObscurationObserver.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 469ab5c092..2bc6faa40c 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -32,8 +32,6 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.event.TechAnalyticsEvent -import com.tangem.core.analytics.models.event.TechAnalyticsEvent.WindowObscured.ObscuredState import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.deeplink.DeepLinksRegistry @@ -224,9 +222,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() - private var isWindowPartiallyObscuredAlreadySent: Boolean = false - private var isWindowFullyObscuredAlreadySent: Boolean = false - override fun onCreate(savedInstanceState: Bundle?) { // We need to call it before onCreate to prevent unnecessary activity recreation installAppTheme() @@ -272,6 +267,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac if (intent != null) { deepLinksRegistry.launch(intent) } + + lifecycle.addObserver(WindowObscurationObserver) } private fun installRouting() { @@ -529,41 +526,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } override fun dispatchTouchEvent(event: MotionEvent): Boolean { - val isPartiallyObscured = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0 - } else { - false - } + val result = WindowObscurationObserver.dispatchTouchEvent(event, analyticsEventsHandler) - if (isPartiallyObscured) { - Timber.d("Window is partially obscured") - - if (!isWindowPartiallyObscuredAlreadySent) { - analyticsEventsHandler.send( - event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.PARTIALLY), - ) - - isWindowPartiallyObscuredAlreadySent = true - } - } - - val isFullyObscured = event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 - - if (isFullyObscured) { - Timber.d("Window is partially or fully obscured") - - if (!isWindowFullyObscuredAlreadySent) { - analyticsEventsHandler.send( - event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.FULLY), - ) - - isWindowFullyObscuredAlreadySent = true - } - - return false - } - - return super.dispatchTouchEvent(event) + return if (result) super.dispatchTouchEvent(event) else false } private fun showSnackbar(text: String, length: Int, buttonTitle: String?, action: View.OnClickListener?) { diff --git a/app/src/main/java/com/tangem/tap/WindowObscurationObserver.kt b/app/src/main/java/com/tangem/tap/WindowObscurationObserver.kt new file mode 100644 index 0000000000..57f5a3fdec --- /dev/null +++ b/app/src/main/java/com/tangem/tap/WindowObscurationObserver.kt @@ -0,0 +1,68 @@ +package com.tangem.tap + +import android.os.Build +import android.view.MotionEvent +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.event.TechAnalyticsEvent +import com.tangem.core.analytics.models.event.TechAnalyticsEvent.WindowObscured.ObscuredState +import timber.log.Timber + +internal object WindowObscurationObserver : DefaultLifecycleObserver { + + private var isWindowPartiallyObscuredAlreadySent: Boolean = false + private var isWindowFullyObscuredAlreadySent: Boolean = false + + private var isReadyToProxy = false + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + isReadyToProxy = true + } + + override fun onPause(owner: LifecycleOwner) { + super.onPause(owner) + isReadyToProxy = false + } + + fun dispatchTouchEvent(event: MotionEvent, analyticsEventHandler: AnalyticsEventHandler): Boolean { + if (!isReadyToProxy) return true + + val isPartiallyObscured = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0 + } else { + false + } + + if (isPartiallyObscured) { + Timber.d("Window is partially obscured") + + if (!isWindowPartiallyObscuredAlreadySent) { + analyticsEventHandler.send( + event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.PARTIALLY), + ) + + isWindowPartiallyObscuredAlreadySent = true + } + } + + val isFullyObscured = event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 + + if (isFullyObscured) { + Timber.d("Window is partially or fully obscured") + + if (!isWindowFullyObscuredAlreadySent) { + analyticsEventHandler.send( + event = TechAnalyticsEvent.WindowObscured(state = ObscuredState.FULLY), + ) + + isWindowFullyObscuredAlreadySent = true + } + + return false + } + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index cde9a3b7df..d4f05a284a 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -212,6 +212,7 @@ internal class MainViewModel @Inject constructor( override fun onDismissBottomSheet() { listenToFlipsUseCase.changeUpdateEnabled(true) + router.pop() stateHolder.updateWithoutModalNotification() stateHolder.updateWithHiddenBalancesToast(true) } From a61968c26cdc6647177b42b06111df0e59b36caa Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Dec 2024 14:55:42 +0300 Subject: [PATCH 34/40] Updated on 2026-08-14 --- .../tokendetails/analytics/TokenDetailsAnalyticsEvent.kt | 2 +- .../tokendetails/analytics/utils/CryptoCurrencyMapper.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt index 367ea0574d..5362f40522 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsAnalyticsEvent.kt @@ -25,7 +25,7 @@ internal open class TokenDetailsAnalyticsEvent( ) class Reveal(currency: CryptoCurrency) : Notice( - event = "Notice - Reveal Transaction", + event = "Reveal Transaction", params = currency.toAnalyticsParams(), ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/utils/CryptoCurrencyMapper.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/utils/CryptoCurrencyMapper.kt index d63835def0..c01c196347 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/utils/CryptoCurrencyMapper.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/utils/CryptoCurrencyMapper.kt @@ -5,6 +5,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency internal fun CryptoCurrency.toAnalyticsParams(): Map { return mapOf( "Token" to symbol, - "Blockchain" to network.currencySymbol, + "Blockchain" to network.name, ) } \ No newline at end of file From a886332e4f3f357a59635a18c03b1f7cc5d3ef58 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Dec 2024 21:40:51 +0400 Subject: [PATCH 35/40] Updated on 2026-08-14 --- features/referral/presentation/build.gradle.kts | 1 + .../main/java/com/tangem/feature/referral/ui/ReferralScreen.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index ef6062abd5..93d3ad159f 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(deps.androidx.appCompat) implementation(deps.androidx.fragment.ktx) implementation(deps.lifecycle.viewModel.ktx) + implementation(deps.androidx.activity.compose) /** Compose */ implementation(deps.compose.foundation) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 9fe33b7bd6..a3a69e7bd8 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -2,6 +2,7 @@ package com.tangem.feature.referral.ui import android.content.res.Configuration import android.content.res.Resources +import androidx.activity.compose.BackHandler import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -52,6 +53,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) { val snackbarHostState = remember(::SnackbarHostState) + BackHandler(onBack = stateHolder.headerState.onBackClicked) + Scaffold( topBar = { AppBarWithBackButton( From 8e3bdacae10209e30760db06d33b0c839e462fc3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Dec 2024 18:31:26 +0300 Subject: [PATCH 36/40] Updated on 2026-08-14 --- core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ru/strings.xml | 4 +++- core/res/src/main/res/values/strings.xml | 2 ++ .../tokens/model/warnings/CryptoCurrencyWarning.kt | 2 ++ .../tangem/domain/tokens/GetCurrencyWarningsUseCase.kt | 10 ++++++++++ .../TokenDetailsNotificationsAnalyticsSender.kt | 1 + .../state/components/TokenDetailsNotification.kt | 5 +++++ .../state/factory/TokenDetailsNotificationConverter.kt | 1 + .../main/java/com/tangem/lib/crypto/BlockchainUtils.kt | 5 +++++ 9 files changed, 30 insertions(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 4972952622..e6647acb4d 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -580,6 +580,7 @@ Cartes identiques Code d\'accès Disponible avec %s + L\'achat de crypto-monnaies sur Tangem est assuré par des fournisseurs tiers selon leurs conditions. Recherche par pays Indisponible Autres devises diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 4bd80de49f..f63d0d3e28 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -51,6 +51,8 @@ Больше не показывать Понятно Балансы скрыты + Согласно информации от разработчиков сети, токены Kaspa находятся в режиме бета. Следите за обновлениями! + Бета режим Пожалуйста, отсканируйте карту или кольцо Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту или кольцо Слишком много попыток @@ -227,7 +229,7 @@ Не выбрано Сеть Сеть - Вы можете добавить токен в ручную, если он не поддерживается Tangem + Вы можете добавить токен вручную, если он не поддерживается Tangem Например, USDC Символ Символ токена diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ede0aab5f3..537be90651 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -51,6 +51,8 @@ Don\'t show again Got it Balances are hidden + According to the blockchain developers, Kaspa tokens are currently in beta. Stay tuned for updates! + Beta Mode Please scan the card or ring Please try again in 30 seconds or scan the card or ring Too many attempts diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 4c4473250c..ccd2264205 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -57,4 +57,6 @@ sealed class CryptoCurrencyWarning { val amount: BigDecimal, val maxAmount: BigDecimal?, ) : CryptoCurrencyWarning() + + data object TokensInBetaWarning : CryptoCurrencyWarning() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 3e54e19951..325bba0998 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -173,6 +173,7 @@ class GetCurrencyWarningsUseCase( when { tokenStatus != null && coinStatus != null -> { buildList { + getIsBetaTokensWarning(tokenStatus.currency)?.let(::add) getFeeWarning( userWalletId = userWalletId, coinStatus = coinStatus, @@ -219,6 +220,15 @@ class GetCurrencyWarningsUseCase( } } + private fun getIsBetaTokensWarning(currency: CryptoCurrency): CryptoCurrencyWarning? { + val isTokenBetaFunctionality = BlockchainUtils.isTokenBetaFunctionality(currency.network.id.value) + return if (isTokenBetaFunctionality) { + CryptoCurrencyWarning.TokensInBetaWarning + } else { + null + } + } + private suspend fun constructTokenBalanceNotEnoughWarning( userWalletId: UserWalletId, tokenStatus: CryptoCurrencyStatus, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 414b838b91..8e3ed14b1c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -53,6 +53,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.HederaAssociateWarning, is TokenDetailsNotification.KoinosMana, is TokenDetailsNotification.MigrationMaticToPol, + is TokenDetailsNotification.TokensInBeta, -> null } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 86db0a1bf3..abc78abbec 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -229,4 +229,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { title = resourceReference(id = R.string.warning_matic_migration_title), subtitle = resourceReference(id = R.string.warning_matic_migration_message), ) + + data object TokensInBeta : Warning( + title = resourceReference(id = R.string.beta_mode_warning_title), + subtitle = resourceReference(id = R.string.beta_mode_warning_message), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index b1435ae301..1c0e9869ad 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -135,6 +135,7 @@ internal class TokenDetailsNotificationConverter( }, ) is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol + is CryptoCurrencyWarning.TokensInBetaWarning -> TokensInBeta } } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 80b0af4a35..5e1a12c171 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -141,4 +141,9 @@ object BlockchainUtils { private fun getNetworkNameWithoutTestnet(blockchain: Blockchain): String { return blockchain.getNetworkName().replace(oldValue = " Testnet", newValue = "") } + + fun isTokenBetaFunctionality(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) + return blockchain == Blockchain.Kaspa + } } \ No newline at end of file From d15e559a41afece041a4968d018480e857a62de2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Dec 2024 20:06:48 +0300 Subject: [PATCH 37/40] Updated on 2026-08-14 --- .../com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 325bba0998..c4904e76b7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -222,7 +222,7 @@ class GetCurrencyWarningsUseCase( private fun getIsBetaTokensWarning(currency: CryptoCurrency): CryptoCurrencyWarning? { val isTokenBetaFunctionality = BlockchainUtils.isTokenBetaFunctionality(currency.network.id.value) - return if (isTokenBetaFunctionality) { + return if (currency is CryptoCurrency.Coin && isTokenBetaFunctionality) { CryptoCurrencyWarning.TokensInBetaWarning } else { null From 30171d868f96d3ccbc6313fb9c3c18788743c044 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 23 Dec 2024 20:07:46 +0300 Subject: [PATCH 38/40] Updated on 2026-08-14 --- .../com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index c4904e76b7..e6975190ee 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -222,7 +222,7 @@ class GetCurrencyWarningsUseCase( private fun getIsBetaTokensWarning(currency: CryptoCurrency): CryptoCurrencyWarning? { val isTokenBetaFunctionality = BlockchainUtils.isTokenBetaFunctionality(currency.network.id.value) - return if (currency is CryptoCurrency.Coin && isTokenBetaFunctionality) { + return if (currency is CryptoCurrency.Token && isTokenBetaFunctionality) { CryptoCurrencyWarning.TokensInBetaWarning } else { null From ebfbfa3c41e3b35bf9803df4ce44b66043b5044d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 24 Dec 2024 10:57:51 +0300 Subject: [PATCH 39/40] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 537be90651..99759792da 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -52,7 +52,7 @@ Got it Balances are hidden According to the blockchain developers, Kaspa tokens are currently in beta. Stay tuned for updates! - Beta Mode + Beta Mode Please scan the card or ring Please try again in 30 seconds or scan the card or ring Too many attempts From e04f0cfa26fceea897904dec03ddda2ef2caffc8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 24 Dec 2024 15:44:19 +0300 Subject: [PATCH 40/40] Updated on 2026-08-14 --- .../wallet/presentation/wallet/ui/WalletEventEffect.kt | 2 ++ .../wallet/presentation/wallet/viewmodels/WalletViewModel.kt | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index 19cd88f97f..5bf812ae5e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -17,6 +17,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling +import kotlinx.coroutines.delay @Suppress("LongParameterList") @Composable @@ -37,6 +38,7 @@ internal fun WalletEventEffect( onTrigger = { value -> when (value) { is WalletEvent.ChangeWallet -> { + delay(timeMillis = 1000) // to let compose time to start drawing otherwise animation doesnt work onAutoScrollSet() walletsListState.animateScrollByIndex(prevIndex = selectedWalletIndex, newIndex = value.index) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 785d29d72e..145ad36baa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -363,7 +363,7 @@ internal class WalletViewModel @Inject constructor( ) } - private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { + private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { walletScreenContentLoader.load( userWallet = action.selectedWallet, clickIntents = clickIntents, @@ -379,8 +379,6 @@ internal class WalletViewModel @Inject constructor( ), ) - withContext(dispatchers.io) { delay(timeMillis = 1000) } - scrollToWallet(index = action.selectedWalletIndex) }