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

@ -58,8 +58,6 @@ import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsL
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
import com.tangem.tap.features.intentHandler.handlers.BuyCurrencyIntentHandler
import com.tangem.tap.features.intentHandler.handlers.SellCurrencyIntentHandler
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.features.main.model.Toast
@ -301,8 +299,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
intentProcessor.addHandler(BuyCurrencyIntentHandler())
intentProcessor.addHandler(SellCurrencyIntentHandler())
}
private fun updateAppTheme(appThemeMode: AppThemeMode) {

View file

@ -1,27 +0,0 @@
package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.tap.features.intentHandler.IntentHandler
/**
[REDACTED_AUTHOR]
*/
class BuyCurrencyIntentHandler : IntentHandler {
override fun handleIntent(intent: Intent?): Boolean {
// FIXME: [REDACTED_JIRA]
// val data = intent?.data ?: return false
// val currency = store.state.walletState.selectedCurrency ?: return false
//
// val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
// return if (data.host == successUri.host && data.authority == successUri.authority) {
// val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
// Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
// true
// } else {
// false
// }
return false
}
}

View file

@ -1,44 +0,0 @@
package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.tap.features.intentHandler.IntentHandler
/**
[REDACTED_AUTHOR]
*/
class SellCurrencyIntentHandler : IntentHandler {
override fun handleIntent(intent: Intent?): Boolean {
// FIXME: [REDACTED_JIRA]
// return try {
// val intentData = intent?.data ?: return false
// val transactionID = intentData.getQueryParameter(TRANSACTION_ID_PARAM) ?: return false
// val currency = intentData.getQueryParameter(CURRENCY_CODE_PARAM) ?: return false
// val amount = intentData.getQueryParameter(CURRENCY_AMOUNT_PARAM) ?: return false
// val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false
//
// Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
// store.dispatchOnMain(
// TradeCryptoAction.SendCrypto(
// currencyId = currency,
// amount = amount,
// destinationAddress = destinationAddress,
// transactionId = transactionID,
// ),
// )
// true
// } catch (exception: Exception) {
// Timber.d("Not MoonPay URL")
// false
// }
return false
}
// private companion object {
// private const val TRANSACTION_ID_PARAM = "transactionId"
// private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
// private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
// private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
// }
}

View file

@ -22,6 +22,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -50,19 +51,18 @@ object TradeCryptoMiddleware {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
is TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen()
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
is TradeCryptoAction.New.Buy -> proceedNewBuyAction(state, action)
is TradeCryptoAction.New.Sell -> proceedNewSellAction(action)
is TradeCryptoAction.New.Swap -> openSwap(
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
is TradeCryptoAction.Sell -> proceedSellAction(action)
is TradeCryptoAction.Swap -> openSwap(
currency = action.cryptoCurrency,
)
is TradeCryptoAction.New.SendToken -> handleNewSendToken(action = action)
is TradeCryptoAction.New.SendCoin -> handleNewSendCoin(action = action)
is TradeCryptoAction.SendToken -> handleSendToken(action = action)
is TradeCryptoAction.SendCoin -> handleSendCoin(action = action)
}
}
private fun proceedNewBuyAction(state: () -> AppState?, action: TradeCryptoAction.New.Buy) {
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
?.let(NetworkAddress.Address::value)
@ -119,7 +119,7 @@ object TradeCryptoMiddleware {
}
}
private fun proceedNewSellAction(action: TradeCryptoAction.New.Sell) {
private fun proceedSellAction(action: TradeCryptoAction.Sell) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
?.let(NetworkAddress.Address::value)
@ -139,33 +139,6 @@ object TradeCryptoMiddleware {
}
}
private fun preconfigureAndOpenSendScreen() = scope.launch {
// FIXME: [REDACTED_JIRA]
// val selectedWalletData = store.state.walletState.selectedWalletData ?: return
//
// Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
// val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
// FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
// return
// }
//
// store.dispatchOnMain(
// PrepareSendScreen(
// walletManager = walletManager,
// coinAmount = walletManager.wallet.amounts[AmountType.Coin],
// coinRate = selectedWalletData.fiatRate,
// ),
// )
// store.dispatchOnMain(
// SendAction.SendSpecificTransaction(
// sendAmount = action.amount,
// destinationAddress = action.destinationAddress,
// transactionId = action.transactionId,
// ),
// )
// store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send))
}
private fun openReceiptUrl(transactionId: String) {
store.dispatchOnMain(NavigationAction.PopBackTo())
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
@ -182,7 +155,7 @@ object TradeCryptoMiddleware {
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
}
private fun handleNewSendToken(action: TradeCryptoAction.New.SendToken) {
private fun handleSendToken(action: TradeCryptoAction.SendToken) {
val currency = action.tokenCurrency
val blockchain = Blockchain.fromId(currency.network.id.value)
@ -221,6 +194,17 @@ object TradeCryptoMiddleware {
),
)
val txInfo = action.transactionInfo
if (txInfo != null) {
store.dispatchOnMain(
SendAction.SendSpecificTransaction(
sendAmount = txInfo.amount,
destinationAddress = txInfo.destinationAddress,
transactionId = txInfo.transactionId,
),
)
}
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
@ -229,7 +213,7 @@ object TradeCryptoMiddleware {
}
}
private fun handleNewSendCoin(action: TradeCryptoAction.New.SendCoin) {
private fun handleSendCoin(action: TradeCryptoAction.SendCoin) {
val cryptoStatus = action.coinStatus
val currency = cryptoStatus.currency
val blockchain = Blockchain.fromId(currency.network.id.value)
@ -278,6 +262,17 @@ object TradeCryptoMiddleware {
is CryptoCurrency.Token -> error("Action.tokenStatus.currency is Token")
}
val txInfo = action.transactionInfo
if (txInfo != null) {
store.dispatchOnMain(
SendAction.SendSpecificTransaction(
sendAmount = txInfo.amount,
destinationAddress = txInfo.destinationAddress,
transactionId = txInfo.transactionId,
),
)
}
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,

1
core/deep-links/global/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,15 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.core.deeplink.global"
}
dependencies {
/* Project */
implementation(projects.core.deepLinks)
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.deeplink.global
import com.tangem.core.deeplink.DeepLink
class BuyCurrencyDeepLink(val onReceive: () -> Unit) : DeepLink {
override val uri: String = "tangem://success.tangem.com"
override fun onReceive(params: Map<String, String>) {
onReceive()
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.core.deeplink.global
import com.tangem.core.deeplink.DeepLink
class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink {
override val uri: String = "tangem://sell-request.tangem.com"
override fun onReceive(params: Map<String, String>) {
val data = Data(
transactionId = params["transactionId"] ?: return,
baseCurrencyAmount = params["baseCurrencyAmount"] ?: return,
depositWalletAddress = params["depositWalletAddress"] ?: return,
)
onReceive(data)
}
data class Data(
val transactionId: String,
val baseCurrencyAmount: String,
val depositWalletAddress: String,
)
}

View file

@ -1,8 +1,8 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
@ -22,7 +22,7 @@ class GetCryptoCurrencyStatusSyncUseCase(
suspend operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
): Either<TokenListError, CryptoCurrencyStatus> {
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
@ -31,6 +31,18 @@ class GetCryptoCurrencyStatusSyncUseCase(
)
return operations.getCurrencyStatusSync(cryptoCurrencyId)
.mapLeft { error -> error.mapToTokenListError() }
.mapLeft { error -> error.mapToCurrencyError() }
}
suspend operator fun invoke(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
)
return operations.getPrimaryCurrencyStatusSync()
.mapLeft { error -> error.mapToCurrencyError() }
}
}

View file

@ -8,43 +8,41 @@ import java.math.BigDecimal
sealed class TradeCryptoAction : Action {
data class SendCrypto(
val currencyId: String,
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
data class Buy(
val userWallet: UserWallet,
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val appCurrencyCode: String,
val checkUserLocation: Boolean = true,
) : TradeCryptoAction()
data class Sell(
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val appCurrencyCode: String,
) : TradeCryptoAction()
data class SendToken(
val userWallet: UserWallet,
val tokenCurrency: CryptoCurrency.Token,
val tokenFiatRate: BigDecimal?,
val coinFiatRate: BigDecimal?,
val feeCurrencyStatus: CryptoCurrencyStatus?,
val transactionInfo: TransactionInfo? = null,
) : TradeCryptoAction()
data class SendCoin(
val userWallet: UserWallet,
val coinStatus: CryptoCurrencyStatus,
val feeCurrencyStatus: CryptoCurrencyStatus?,
val transactionInfo: TransactionInfo? = null,
) : TradeCryptoAction()
data class Swap(val cryptoCurrency: CryptoCurrency) : TradeCryptoAction()
data class TransactionInfo(
val amount: String,
val destinationAddress: String,
val transactionId: String,
) : TradeCryptoAction()
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
sealed class New : TradeCryptoAction() {
data class Buy(
val userWallet: UserWallet,
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val appCurrencyCode: String,
val checkUserLocation: Boolean = true,
) : New()
data class Sell(
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val appCurrencyCode: String,
) : New()
data class SendToken(
val userWallet: UserWallet,
val tokenCurrency: CryptoCurrency.Token,
val tokenFiatRate: BigDecimal?,
val coinFiatRate: BigDecimal?,
val feeCurrencyStatus: CryptoCurrencyStatus?,
) : New()
data class SendCoin(
val userWallet: UserWallet,
val coinStatus: CryptoCurrencyStatus,
val feeCurrencyStatus: CryptoCurrencyStatus?,
) : New()
data class Swap(val cryptoCurrency: CryptoCurrency) : New()
}
)
}

View file

@ -11,6 +11,8 @@ import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
// FIXME: Refactor - [REDACTED_JIRA]
@Suppress("LargeClass")
internal class CurrenciesStatusesOperations(
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
@ -107,6 +109,27 @@ internal class CurrenciesStatusesOperations(
}
}
suspend fun getPrimaryCurrencyStatusSync(): Either<Error, CryptoCurrencyStatus> = either {
val currency = catch(
block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) },
catch = { raise(Error.DataError(it)) },
)
val quotes = catch(
block = { quotesRepository.getQuoteSync(currency.id).right() },
catch = { Error.DataError(it).left() },
)
val networkStatus = catch(
block = {
networksRepository.getNetworkStatusesSync(userWalletId, setOf(currency.network))
.firstOrNull { it.network == currency.network }
.right()
},
catch = { Error.DataError(it).left() },
)
return createCurrencyStatus(currency, quotes, networkStatus)
}
fun getCardCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return flow {
val nonEmptyCurrencies = recover(

View file

@ -41,7 +41,7 @@ interface NetworksRepository {
suspend fun getNetworkStatusesSync(
userWalletId: UserWalletId,
networks: Set<Network>,
refresh: Boolean,
refresh: Boolean = false,
): Set<NetworkStatus>
fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean

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() {

View file

@ -71,6 +71,7 @@ include(":core:res")
include(":core:ui")
include(":core:utils")
include(":core:deep-links")
include(":core:deep-links:global")
// endregion Core modules
// region Libs modules