Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-31 12:13:47 +03:00
parent d995d1dd21
commit 07bf6c376c
20 changed files with 386 additions and 174 deletions

View file

@ -51,6 +51,8 @@ dependencies {
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.datasource)
implementation(projects.core.deepLinks)
implementation(projects.core.deepLinks.global)
/** Domain modules */
implementation(projects.domain.appCurrency)

View file

@ -9,6 +9,9 @@ import arrow.core.getOrElse
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.AddressType
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.resourceReference
@ -22,6 +25,7 @@ import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
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
@ -87,6 +91,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
deepLinksRegistry: DeepLinksRegistry,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
@ -148,6 +153,34 @@ internal class TokenDetailsViewModel @Inject constructor(
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
private set
init {
deepLinksRegistry.registerWithViewModel(
viewModel = this,
deepLinks = listOf(
BuyCurrencyDeepLink(::onBuyCurrencyDeepLink),
SellCurrencyDeepLink(::onSellCurrencyDeepLink),
),
)
}
private fun onBuyCurrencyDeepLink() {
val currency = cryptoCurrencyStatus?.currency ?: return
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Bought(currency.symbol))
}
private fun onSellCurrencyDeepLink(data: SellCurrencyDeepLink.Data) {
sendCurrency(
status = cryptoCurrencyStatus ?: return,
transactionInfo = data.let {
TransactionInfo(
amount = it.baseCurrencyAmount,
transactionId = it.transactionId,
destinationAddress = it.depositWalletAddress,
)
},
)
}
override fun onCreate(owner: LifecycleOwner) {
analyticsEventsHandler.send(
event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol),
@ -330,7 +363,7 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.New.Buy(
TradeCryptoAction.Buy(
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
@ -354,27 +387,31 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onSendClick() {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrency.symbol))
val cryptoCurrencyStatus = cryptoCurrencyStatus ?: return
sendCurrency(status = cryptoCurrencyStatus ?: return)
}
private fun sendCurrency(status: CryptoCurrencyStatus, transactionInfo: TransactionInfo? = null) {
viewModelScope.launch(dispatchers.main) {
val maybeFeeCurrencyStatus =
getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, cryptoCurrencyStatus).getOrNull()
getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull()
when (val currency = cryptoCurrencyStatus.currency) {
when (val currency = status.currency) {
is CryptoCurrency.Coin -> {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
action = TradeCryptoAction.SendCoin(
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch },
coinStatus = cryptoCurrencyStatus,
coinStatus = status,
feeCurrencyStatus = maybeFeeCurrencyStatus,
transactionInfo = transactionInfo,
),
)
}
is CryptoCurrency.Token -> {
sendToken(
tokenCurrency = currency,
tokenFiatRate = cryptoCurrencyStatus.value.fiatRate,
tokenFiatRate = status.value.fiatRate,
feeCurrencyStatus = maybeFeeCurrencyStatus,
transactionInfo = transactionInfo,
)
}
}
@ -385,6 +422,7 @@ internal class TokenDetailsViewModel @Inject constructor(
tokenCurrency: CryptoCurrency.Token,
tokenFiatRate: BigDecimal?,
feeCurrencyStatus: CryptoCurrencyStatus?,
transactionInfo: TransactionInfo?,
) {
viewModelScope.launch(dispatchers.io) {
val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch }
@ -399,7 +437,7 @@ internal class TokenDetailsViewModel @Inject constructor(
.firstOrNull()
reduxStateHolder.dispatchWithMain(
action = TradeCryptoAction.New.SendToken(
action = TradeCryptoAction.SendToken(
userWallet = wallet,
tokenCurrency = tokenCurrency,
tokenFiatRate = tokenFiatRate,
@ -408,6 +446,7 @@ internal class TokenDetailsViewModel @Inject constructor(
ifRight = { it.value.fiatRate },
),
feeCurrencyStatus = feeCurrencyStatus,
transactionInfo = transactionInfo,
),
)
}
@ -440,7 +479,7 @@ internal class TokenDetailsViewModel @Inject constructor(
val status = cryptoCurrencyStatus ?: return@showErrorIfDemoModeOrElse
reduxStateHolder.dispatch(
TradeCryptoAction.New.Sell(
TradeCryptoAction.Sell(
cryptoCurrencyStatus = status,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),
@ -451,7 +490,7 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onSwapClick() {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrency.symbol))
reduxStateHolder.dispatch(TradeCryptoAction.New.Swap(cryptoCurrency))
reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrency))
}
override fun onDismissDialog() {

View file

@ -49,6 +49,8 @@ dependencies {
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.common)
implementation(projects.core.deepLinks)
implementation(projects.core.deepLinks.global)
/** Domain modules */
implementation(projects.domain.card)

View file

@ -0,0 +1,156 @@
package com.tangem.feature.wallet.presentation.deeplink
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
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.models.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
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 getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val reduxStateHolder: ReduxStateHolder,
) {
private var deepLinks: List<DeepLink> = emptyList()
fun registerForSingleCurrencyWallets(viewModel: ViewModel, userWallet: UserWallet) {
if (userWallet.isMultiCurrency) {
deepLinksRegistry.unregister(deepLinks)
} else {
if (deepLinks.isEmpty()) {
deepLinks = getDeepLinks(userWallet, viewModel.viewModelScope)
}
deepLinksRegistry.register(deepLinks)
}
viewModel.addCloseable {
deepLinksRegistry.unregister(deepLinks)
}
}
private fun getDeepLinks(userWallet: UserWallet, scope: CoroutineScope): List<DeepLink> {
val sellCurrencyDeepLink = SellCurrencyDeepLink(
onReceive = { data ->
scope.launch {
onSellCurrencyDeepLink(userWallet, data)
}
},
)
val buyCurrencyDeepLink = BuyCurrencyDeepLink(
onReceive = {
scope.launch {
onBuyCurrencyDeepLink(userWallet)
}
},
)
return listOf(sellCurrencyDeepLink, buyCurrencyDeepLink)
}
private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) {
val cryptoCurrencyStatus = getCryptoCurrencyStatusSyncUseCase(userWallet.walletId)
.getOrNull() ?: return
val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWallet.walletId,
cryptoCurrencyStatus,
).getOrNull()
val transactionInfo = data.let {
TransactionInfo(
amount = it.baseCurrencyAmount,
destinationAddress = it.depositWalletAddress,
transactionId = it.transactionId,
)
}
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,
)
}
}
private suspend fun onBuyCurrencyDeepLink(userWallet: UserWallet) {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrNull() ?: return
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
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,
),
)
}
}

View file

@ -609,7 +609,7 @@ internal class WalletViewModel @Inject constructor(
showErrorIfDemoModeOrElse {
reduxStateHolder.dispatch(
TradeCryptoAction.New.Buy(
TradeCryptoAction.Buy(
userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex),
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
@ -623,7 +623,7 @@ internal class WalletViewModel @Inject constructor(
event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol),
)
reduxStateHolder.dispatch(TradeCryptoAction.New.Swap(cryptoCurrencyStatus.currency))
reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrencyStatus.currency))
}
override fun onSingleCurrencySendClick(cryptoCurrencyStatus: CryptoCurrencyStatus?) {
@ -641,7 +641,7 @@ internal class WalletViewModel @Inject constructor(
val maybeFeeCurrencyStatus =
getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, coinStatus).getOrNull()
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
action = TradeCryptoAction.SendCoin(
userWallet = userWallet,
coinStatus = coinStatus,
feeCurrencyStatus = maybeFeeCurrencyStatus,
@ -665,7 +665,7 @@ internal class WalletViewModel @Inject constructor(
is CryptoCurrency.Coin -> {
uiState = stateFactory.getStateWithClosedBottomSheet()
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
action = TradeCryptoAction.SendCoin(
userWallet = userWallet,
coinStatus = cryptoCurrencyStatus,
feeCurrencyStatus = maybeFeeCurrencyStatus,
@ -694,7 +694,7 @@ internal class WalletViewModel @Inject constructor(
it.onRight { coinStatus ->
uiState = stateFactory.getStateWithClosedBottomSheet()
reduxStateHolder.dispatchWithMain(
action = TradeCryptoAction.New.SendToken(
action = TradeCryptoAction.SendToken(
userWallet = userWallet,
tokenCurrency = requireNotNull(cryptoCurrencyStatus.currency as? CryptoCurrency.Token),
tokenFiatRate = cryptoCurrencyStatus.value.fiatRate,
@ -772,7 +772,7 @@ internal class WalletViewModel @Inject constructor(
showErrorIfDemoModeOrElse {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.Sell(
action = TradeCryptoAction.Sell(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = selectedAppCurrencyFlow.value.code,
),

View file

@ -13,6 +13,7 @@ import com.tangem.domain.walletconnect.WalletConnectActions
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler
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
@ -57,6 +58,7 @@ internal class WalletViewModelV2 @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val screenLifecycleProvider: ScreenLifecycleProvider,
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
private val walletDeepLinksHandler: WalletDeepLinksHandler,
) : ViewModel() {
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
@ -157,6 +159,11 @@ internal class WalletViewModelV2 @Inject constructor(
selectedWalletAnalyticsSender.send(selectedWallet)
}
walletDeepLinksHandler.registerForSingleCurrencyWallets(
viewModel = this,
userWallet = selectedWallet,
)
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)

View file

@ -110,7 +110,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
feeCurrencyStatus: CryptoCurrencyStatus?,
) {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendCoin(
action = TradeCryptoAction.SendCoin(
userWallet = userWallet,
coinStatus = cryptoCurrencyStatus,
feeCurrencyStatus = feeCurrencyStatus,
@ -135,7 +135,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
.collectLatest {
it.onRight { coinStatus ->
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.SendToken(
action = TradeCryptoAction.SendToken(
userWallet = userWallet,
tokenCurrency = cryptoCurrency,
tokenFiatRate = cryptoCurrencyStatus.fiatRate,
@ -281,7 +281,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse {
viewModelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
action = TradeCryptoAction.New.Sell(
action = TradeCryptoAction.Sell(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
),
@ -300,7 +300,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse {
viewModelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.New.Buy(
TradeCryptoAction.Buy(
userWallet = userWallet,
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
@ -315,7 +315,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol),
)
reduxStateHolder.dispatch(TradeCryptoAction.New.Swap(cryptoCurrencyStatus.currency))
reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrencyStatus.currency))
}
override fun onExploreClick() {