Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-07 20:17:00 +04:00
parent 0c56df0075
commit 535bb9b8c3
44 changed files with 128 additions and 1038 deletions

View file

@ -23,7 +23,6 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
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,14 +40,10 @@ 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 shareManager: ShareManager,
) {
private val disabledActionsInDemoMode = buildSet {
if (!onrampFeatureToggles.isFeatureEnabled) {
add(TokenActionsBSContentUM.Action.Buy)
}
add(TokenActionsBSContentUM.Action.Sell)
}
@ -132,12 +127,11 @@ internal class TokenActionsHandler @AssistedInject constructor(
}
private fun onBuyClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
userWallet = cryptoCurrencyData.userWallet,
router.push(
AppRoute.Onramp(
userWalletId = cryptoCurrencyData.userWallet.walletId,
currency = cryptoCurrencyData.status.currency,
source = OnrampSource.MARKETS,
cryptoCurrencyStatus = cryptoCurrencyData.status,
appCurrencyCode = currentAppCurrency().code,
),
)
}

View file

@ -1,6 +0,0 @@
package com.tangem.features.onramp
interface OnrampFeatureToggles {
val isFeatureEnabled: Boolean
}

View file

@ -1,10 +0,0 @@
package com.tangem.features.onramp.deeplink
import kotlinx.coroutines.CoroutineScope
interface BuyRedirectDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope): BuyRedirectDeepLinkHandler
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.features.onramp
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
internal class DefaultOnrampFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : OnrampFeatureToggles {
override val isFeatureEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "ONRAMP_ENABLED")
}

View file

@ -1,19 +0,0 @@
package com.tangem.features.onramp
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object OnrampFeatureModule {
@Provides
@Singleton
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): OnrampFeatureToggles {
return DefaultOnrampFeatureToggles(featureTogglesManager)
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.features.onramp.deeplink
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
internal class DefaultBuyRedirectDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
onrampFeatureToggles: OnrampFeatureToggles,
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
analyticsEventHandler: AnalyticsEventHandler,
) : BuyRedirectDeepLinkHandler {
init {
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
getSelectedWalletSyncUseCase().fold(
ifLeft = {
Timber.e("Error on getting user wallet: $it")
},
ifRight = { userWallet ->
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
scope.launch {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrElse {
Timber.e("Error on getting cryptoCurrency: $it")
return@launch
}
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
}
},
)
}
@AssistedFactory
interface Factory : BuyRedirectDeepLinkHandler.Factory {
override fun create(coroutineScope: CoroutineScope): DefaultBuyRedirectDeepLinkHandler
}
}

View file

@ -19,12 +19,6 @@ internal interface OnrampDeeplinkModule {
@Singleton
fun bindOnrampDeepLinkHandlerFactory(impl: DefaultOnrampDeepLinkHandler.Factory): OnrampDeepLinkHandler.Factory
@Binds
@Singleton
fun bindBuyRedirectDeepLinkHandler(
impl: DefaultBuyRedirectDeepLinkHandler.Factory,
): BuyRedirectDeepLinkHandler.Factory
@Binds
@Singleton
fun bindBuyDeepLinkHandler(impl: DefaultBuyDeepLinkHandler.Factory): BuyDeepLinkHandler.Factory

View file

@ -1,6 +1,7 @@
package com.tangem.features.onramp.selecttoken.model
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -23,7 +24,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selecttoken.OnrampOperationComponent.Params
import com.tangem.features.onramp.selecttoken.entity.OnrampOperationUM
@ -46,7 +46,6 @@ internal class OnrampOperationModel @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val messageSender: UiMessageSender,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val rampStateManager: RampStateManager,
) : Model() {
@ -100,7 +99,7 @@ internal class OnrampOperationModel @Inject constructor(
}
private fun selectTokenIfDemoModeOff(status: CryptoCurrencyStatus) {
if (params is Params.Sell || !onrampFeatureToggles.isFeatureEnabled) {
if (params is Params.Sell) {
showErrorIfDemoModeOrElse { selectToken(status) }
} else {
selectToken(status)
@ -109,14 +108,25 @@ internal class OnrampOperationModel @Inject constructor(
private fun selectToken(status: CryptoCurrencyStatus) {
modelScope.launch {
val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code
when (params) {
is Params.Buy -> {
router.push(
AppRoute.Onramp(
userWalletId = selectedUserWallet.walletId,
currency = status.currency,
source = OnrampSource.ACTION_BUTTONS,
),
)
}
is Params.Sell -> {
val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync()
.getOrElse { AppCurrency.Default }.code
reduxStateHolder.dispatch(
action = when (params) {
is Params.Buy -> getBuyAction(status, appCurrencyCode)
is Params.Sell -> TradeCryptoAction.Sell(status, appCurrencyCode)
},
)
reduxStateHolder.dispatch(
action = TradeCryptoAction.Sell(status, appCurrencyCode),
)
}
}
}
}
@ -144,15 +154,6 @@ internal class OnrampOperationModel @Inject constructor(
router.pop()
}
private fun getBuyAction(status: CryptoCurrencyStatus, appCurrencyCode: String): TradeCryptoAction {
return TradeCryptoAction.Buy(
userWallet = selectedUserWallet,
cryptoCurrencyStatus = status,
source = OnrampSource.ACTION_BUTTONS,
appCurrencyCode = appCurrencyCode,
)
}
private fun showErrorIfDemoModeOrElse(action: () -> Unit) {
if (isDemoCardUseCase(cardId = selectedUserWallet.cardId)) {
val alertUM = AlertDemoModeUM(onConfirmClick = {})

View file

@ -5,19 +5,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
import com.tangem.core.deeplink.utils.registerDeepLinks
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import dagger.assisted.Assisted
@ -30,9 +25,6 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
@Assisted params: TokenDetailsComponent.Params,
tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory,
txHistoryComponentFactory: TxHistoryComponent.Factory,
onrampFeatureToggles: OnrampFeatureToggles,
routingFeatureToggle: RoutingFeatureToggle,
deepLinksRegistry: DeepLinksRegistry,
) : TokenDetailsComponent, AppComponentContext by appComponentContext {
private val model: TokenDetailsModel = getOrCreateModel(params)
@ -50,23 +42,6 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
onPause = model::onPause,
onResume = model::onResume,
)
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
val deeplinks = buildList {
if (!onrampFeatureToggles.isFeatureEnabled) {
add(
BuyCurrencyDeepLink(
onReceive = model::onBuyCurrencyDeepLink,
),
)
}
}
registerDeepLinks(
registry = deepLinksRegistry,
deepLinks = deeplinks,
)
}
}
private val tokenMarketBlockComponent = params.currency.toTokenMarketParam()?.let { tokenMarketParams ->

View file

@ -78,7 +78,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBala
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
@ -125,7 +124,6 @@ internal class TokenDetailsModel @Inject constructor(
private val analyticsEventsHandler: AnalyticsEventHandler,
private val vibratorHapticManager: VibratorHapticManager,
private val clipboardManager: ClipboardManager,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val shareManager: ShareManager,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
@ -207,11 +205,6 @@ internal class TokenDetailsModel @Inject constructor(
checkForActionUpdates()
}
fun onBuyCurrencyDeepLink() {
val currency = cryptoCurrencyStatus?.currency ?: return
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Bought(currency.symbol))
}
fun onPause() {
expressTxStatusTaskScheduler.cancelTask()
expressTxJobHolder.cancel()
@ -496,30 +489,14 @@ internal class TokenDetailsModel @Inject constructor(
}
val status = cryptoCurrencyStatus ?: return
if (onrampFeatureToggles.isFeatureEnabled) {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
userWallet = userWallet,
source = OnrampSource.TOKEN_DETAILS,
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),
)
}
} else {
showErrorIfDemoModeOrElse {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
userWallet = userWallet,
source = OnrampSource.TOKEN_DETAILS,
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),
)
}
}
modelScope.launch {
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
currency = status.currency,
source = OnrampSource.TOKEN_DETAILS,
),
)
}
}

View file

@ -24,7 +24,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
import com.tangem.features.onramp.OnrampFeatureToggles
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -52,7 +51,6 @@ internal class WalletClickIntents @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview,
private val rampStateManager: RampStateManager,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val onrampStatusFactory: OnrampStatusFactory,
) : BaseWalletClickIntents(),
@ -157,10 +155,6 @@ internal class WalletClickIntents @Inject constructor(
}
buildList {
if (!onrampFeatureToggles.isFeatureEnabled) {
async { rampStateManager.fetchBuyServiceData() }.let(::add)
}
async { rampStateManager.fetchSellServiceData() }.let(::add)
async { fetchHotCryptoUseCase() }.let(::add)

View file

@ -61,7 +61,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -129,7 +128,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val shareManager: ShareManager,
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val onrampFeatureToggles: OnrampFeatureToggles,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
@ -345,28 +343,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
if (handleUnavailabilityReason(unavailabilityReason)) return
if (onrampFeatureToggles.isFeatureEnabled) {
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
)
} else {
showErrorIfDemoModeOrElse {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
userWallet = userWallet,
source = OnrampSource.TOKEN_LONG_TAP,
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
),
)
}
}
}
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
)
}
override fun onSwapClick(
@ -482,11 +465,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String) {
onMultiWalletActionClick(
statusFlow = if (onrampFeatureToggles.isFeatureEnabled) {
rampStateManager.getExpressInitializationStatus(userWalletId)
} else {
rampStateManager.getBuyInitializationStatus()
},
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
eventCreator = { MainScreenAnalyticsEvent.ButtonBuy(status = it, screenType = screenType) },
)

View file

@ -2,17 +2,12 @@ package com.tangem.feature.wallet.presentation.deeplink
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.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.domain.wallets.models.isMultiCurrency
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.launchOnCancellation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -22,9 +17,7 @@ import javax.inject.Inject
@Suppress("LongParameterList")
internal class WalletDeepLinksHandler @Inject constructor(
private val deepLinksRegistry: DeepLinksRegistry,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val router: AppRouter,
) {
@ -55,15 +48,6 @@ internal class WalletDeepLinksHandler @Inject constructor(
return buildList {
add(sellCurrencyDeepLink)
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
add(
BuyCurrencyDeepLink(
onReceive = {
scope.launch { onBuyCurrencyDeepLink(userWallet) }
},
),
)
}
}
}
@ -86,9 +70,4 @@ internal class WalletDeepLinksHandler @Inject constructor(
router.push(route)
}
private suspend fun onBuyCurrencyDeepLink(userWallet: UserWallet) {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrNull() ?: return
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
}