diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 74f347ee5f..4ef519edbd 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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) { diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt deleted file mode 100644 index 31a7657c79..0000000000 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt deleted file mode 100644 index 52e8e12a33..0000000000 --- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/SellCurrencyIntentHandler.kt +++ /dev/null @@ -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" - // } -} \ No newline at end of file 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 b1db79189b..36123cf1a9 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 @@ -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, diff --git a/core/deep-links/global/.gitignore b/core/deep-links/global/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/core/deep-links/global/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/deep-links/global/build.gradle.kts b/core/deep-links/global/build.gradle.kts new file mode 100644 index 0000000000..ac47c5ac3d --- /dev/null +++ b/core/deep-links/global/build.gradle.kts @@ -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) +} \ No newline at end of file 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 new file mode 100644 index 0000000000..286f8f8c73 --- /dev/null +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt @@ -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) { + onReceive() + } +} \ No newline at end of file diff --git a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt new file mode 100644 index 0000000000..5d9047a83b --- /dev/null +++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/SellCurrencyDeepLink.kt @@ -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) { + 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, + ) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt index ffe6e79c3b..ed3cbfad6c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt @@ -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 { + ): Either { 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 { + val operations = CurrenciesStatusesOperations( + userWalletId = userWalletId, + currenciesRepository = currenciesRepository, + quotesRepository = quotesRepository, + networksRepository = networksRepository, + ) + + return operations.getPrimaryCurrencyStatusSync() + .mapLeft { error -> error.mapToCurrencyError() } } } \ 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 85abbb2bc8..1657a3ea05 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 @@ -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() - } + ) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index a86698763b..92d6969de0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -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 = 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>> { return flow { val nonEmptyCurrencies = recover( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index 16eb05e84a..6ec3fbb11a 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -41,7 +41,7 @@ interface NetworksRepository { suspend fun getNetworkStatusesSync( userWalletId: UserWalletId, networks: Set, - refresh: Boolean, + refresh: Boolean = false, ): Set fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 860b64eec4..81f6868424 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -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) 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 7a7d53ba09..1fba010ecc 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 @@ -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() { diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index a3207eb5cd..decfa1c37e 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) 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 new file mode 100644 index 0000000000..0248645642 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -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 = 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 { + 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, + ), + ) + } +} \ No newline at end of file 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 065a47b196..8adfe05590 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 @@ -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, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt index ae097c7991..418da4487e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModelV2.kt @@ -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 = 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) 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 3fd26c242b..198af9b84d 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 @@ -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() { diff --git a/settings.gradle.kts b/settings.gradle.kts index 7060d410f2..3ac4a3dcf4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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