diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 826b723d16..13f1801543 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -154,6 +154,28 @@ android:scheme="tangem" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt index 4eb339d9e0..45bde26900 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -8,6 +8,8 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn @@ -20,6 +22,7 @@ import kotlinx.coroutines.flow.transformLatest import timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") @ActivityScoped internal class DeepLinkFactory @Inject constructor( private val onrampDeepLink: OnrampDeepLinkHandler.Factory, @@ -27,6 +30,8 @@ internal class DeepLinkFactory @Inject constructor( private val buyDeepLink: BuyDeepLinkHandler.Factory, private val referralDeepLink: ReferralDeepLinkHandler.Factory, private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory, + private val walletDeepLink: WalletDeepLinkHandler.Factory, + private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory, ) { private val permittedAppRoute = MutableStateFlow(false) @@ -88,25 +93,27 @@ internal class DeepLinkFactory @Inject constructor( } private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope) { - val params = getParams(deeplinkUri) + val queryParams = getQueryParams(deeplinkUri) when (deeplinkUri.host) { - DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, params) - DeepLinkRoute.Sell.host -> sellDeepLink.create(coroutineScope, params) + DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams) + DeepLinkRoute.Sell.host -> sellDeepLink.create(coroutineScope, queryParams) DeepLinkRoute.Buy.host -> buyDeepLink.create(coroutineScope) DeepLinkRoute.Referral.host -> referralDeepLink.create() + DeepLinkRoute.Wallet.host -> walletDeepLink.create() + DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(coroutineScope, queryParams) else -> { Timber.i( """ No match found for deep link |- Received URI: $deeplinkUri - |- With params: $params + |- With params: $queryParams """.trimIndent(), ) } } } - private fun getParams(uri: Uri): Map { + private fun getQueryParams(uri: Uri): Map { val params = mutableMapOf() uri.queryParameterNames.forEach { paramName -> diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt index 03badaf8ef..32be06bd2f 100644 --- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -6,6 +6,8 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler import io.mockk.every import io.mockk.mockk @@ -37,6 +39,12 @@ class DeepLinkFactoryTest { private val walletConnectDeepLinkFactory = mockk(relaxed = true) { every { create(any()) } returns mockk() } + private val walletDeepLinkFactory = mockk(relaxed = true) { + every { create() } returns mockk() + } + private val tokenDetailsDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } private val mockedUri = mockk(relaxed = true) @@ -49,6 +57,8 @@ class DeepLinkFactoryTest { buyDeepLinkFactory, referralDeepLinkFactory, walletConnectDeepLinkFactory, + walletDeepLinkFactory, + tokenDetailsDeepLinkFactory, ) @OptIn(ExperimentalCoroutinesApi::class) @@ -162,6 +172,8 @@ class DeepLinkFactoryTest { buyDeepLinkFactory.create(any()) referralDeepLinkFactory.create() walletConnectDeepLinkFactory.create(any()) + walletDeepLinkFactory.create() + tokenDetailsDeepLinkFactory.create(any(), any()) } } @@ -186,6 +198,12 @@ class DeepLinkFactoryTest { advanceUntilIdle() verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + // Test Token Details + every { mockedUri.host } returns "token" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { tokenDetailsDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + // Reset params every { mockedUri.queryParameterNames } returns emptySet() every { mockedUri.getQueryParameter(any()) } returns "" @@ -201,6 +219,12 @@ class DeepLinkFactoryTest { deepLinkFactory.handleDeeplink(mockedUri, testScope) advanceUntilIdle() verify { referralDeepLinkFactory.create() } + + // Test Wallet + every { mockedUri.host } returns "main" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { walletDeepLinkFactory.create() } } @Test @@ -219,6 +243,8 @@ class DeepLinkFactoryTest { buyDeepLinkFactory.create(any()) referralDeepLinkFactory.create() walletConnectDeepLinkFactory.create(any()) + walletDeepLinkFactory.create() + tokenDetailsDeepLinkFactory.create(any(), any()) } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt index 5eb9bafa87..c683a4136d 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt @@ -19,6 +19,14 @@ sealed class DeepLinkRoute { data object Referral : DeepLinkRoute() { override val host: String = "referral" } + + data object Wallet : DeepLinkRoute() { + override val host: String = "main" + } + + data object TokenDetails : DeepLinkRoute() { + override val host: String = "token" + } } enum class DeepLinkScheme(val scheme: String) { diff --git a/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/NotificationType.kt b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/NotificationType.kt new file mode 100644 index 0000000000..ac0ce65b35 --- /dev/null +++ b/domain/notifications/models/src/main/java/com/tangem/domain/notifications/models/NotificationType.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.notifications.models + +enum class NotificationType(val type: String) { + Promo("promo"), + Unknown("unknown"), + ; + + companion object { + fun getType(type: String?): NotificationType { + return entries.firstOrNull { it.type == type } ?: Unknown + } + } +} \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt index e590b31392..8a45fc89ef 100644 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/deeplink/OnrampDeepLink.kt @@ -15,6 +15,6 @@ abstract class OnrampDeepLink : DeepLink() { interface OnrampDeepLinkHandler { interface Factory { - fun create(coroutineScope: CoroutineScope, params: Map): OnrampDeepLinkHandler + fun create(coroutineScope: CoroutineScope, queryParams: Map): OnrampDeepLinkHandler } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLinkHandler.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLinkHandler.kt index 47b4d78b20..035c32ef13 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLinkHandler.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/deeplink/DefaultOnrampDeepLinkHandler.kt @@ -12,14 +12,14 @@ import timber.log.Timber internal class DefaultOnrampDeepLinkHandler @AssistedInject constructor( @Assisted scope: CoroutineScope, - @Assisted params: Map, + @Assisted queryParams: Map, appRouter: AppRouter, onrampSuccessScreenTrigger: OnrampSuccessScreenTrigger, ) : OnrampDeepLinkHandler { init { - val txId = params[TX_ID_KEY] - val result = OnrampRedirectResult.getResult(params[RESULT_KEY]) + val txId = queryParams[TX_ID_KEY] + val result = OnrampRedirectResult.getResult(queryParams[RESULT_KEY]) when { !txId.isNullOrEmpty() -> { @@ -39,7 +39,7 @@ internal class DefaultOnrampDeepLinkHandler @AssistedInject constructor( Timber.e( """ Invalid parameters for ONRAMP deeplink - |- Params: $params + |- Params: $queryParams """.trimIndent(), ) } @@ -48,7 +48,10 @@ internal class DefaultOnrampDeepLinkHandler @AssistedInject constructor( @AssistedFactory interface Factory : OnrampDeepLinkHandler.Factory { - override fun create(coroutineScope: CoroutineScope, params: Map): DefaultOnrampDeepLinkHandler + override fun create( + coroutineScope: CoroutineScope, + queryParams: Map, + ): DefaultOnrampDeepLinkHandler } private companion object { diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/deeplink/SellDeepLinkHandler.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/deeplink/SellDeepLinkHandler.kt index d645fff429..32d263693a 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/deeplink/SellDeepLinkHandler.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/deeplink/SellDeepLinkHandler.kt @@ -5,6 +5,6 @@ import kotlinx.coroutines.CoroutineScope interface SellDeepLinkHandler { interface Factory { - fun create(coroutineScope: CoroutineScope, params: Map): SellDeepLinkHandler + fun create(coroutineScope: CoroutineScope, queryParams: Map): SellDeepLinkHandler } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/deeplink/DefaultSellDeepLinkHandler.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/deeplink/DefaultSellDeepLinkHandler.kt index e86282c818..49aeba8204 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/deeplink/DefaultSellDeepLinkHandler.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/deeplink/DefaultSellDeepLinkHandler.kt @@ -16,18 +16,18 @@ import timber.log.Timber @Suppress("ComplexCondition") internal class DefaultSellDeepLinkHandler @AssistedInject constructor( @Assisted scope: CoroutineScope, - @Assisted params: Map, + @Assisted queryParams: Map, appRouter: AppRouter, getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, ) : SellDeepLinkHandler { init { - val currencyId = params[CURRENCY_ID_KEY] - val transactionId = params[TRANSACTION_ID_KEY] - val amount = params[AMOUNT_KEY] - val destinationAddress = params[DESTINATION_ADDRESS_KEY] - val memo = params[MEMO_KEY] + val currencyId = queryParams[CURRENCY_ID_KEY] + val transactionId = queryParams[TRANSACTION_ID_KEY] + val amount = queryParams[AMOUNT_KEY] + val destinationAddress = queryParams[DESTINATION_ADDRESS_KEY] + val memo = queryParams[MEMO_KEY] // It is okay here, we are navigating from outside, and there is no other way to getting UserWallet getSelectedWalletSyncUseCase() @@ -42,7 +42,7 @@ internal class DefaultSellDeepLinkHandler @AssistedInject constructor( Timber.e( """ Invalid parameters for SELL deeplink - |- Params: $params + |- Params: $queryParams """.trimIndent(), ) return@fold @@ -71,7 +71,10 @@ internal class DefaultSellDeepLinkHandler @AssistedInject constructor( @AssistedFactory interface Factory : SellDeepLinkHandler.Factory { - override fun create(coroutineScope: CoroutineScope, params: Map): DefaultSellDeepLinkHandler + override fun create( + coroutineScope: CoroutineScope, + queryParams: Map, + ): DefaultSellDeepLinkHandler } private companion object { diff --git a/features/tokendetails/api/build.gradle.kts b/features/tokendetails/api/build.gradle.kts index 47558ef9f7..0551d08f54 100644 --- a/features/tokendetails/api/build.gradle.kts +++ b/features/tokendetails/api/build.gradle.kts @@ -18,4 +18,7 @@ dependencies { api(projects.domain.models) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + + /** Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/deeplink/TokenDetailsDeepLinkHandler.kt b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/deeplink/TokenDetailsDeepLinkHandler.kt new file mode 100644 index 0000000000..52b739b46c --- /dev/null +++ b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/deeplink/TokenDetailsDeepLinkHandler.kt @@ -0,0 +1,10 @@ +package com.tangem.features.tokendetails.deeplink + +import kotlinx.coroutines.CoroutineScope + +interface TokenDetailsDeepLinkHandler { + + interface Factory { + fun create(coroutineScope: CoroutineScope, queryParams: Map): TokenDetailsDeepLinkHandler + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index b80dda2c34..3515424d77 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -87,6 +87,7 @@ dependencies { implementation(projects.domain.promo) implementation(projects.domain.promo.models) implementation(projects.domain.quotes) + implementation(projects.domain.notifications.models) /** Temp dependency to swap domain */ implementation(projects.features.swap.domain) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt new file mode 100644 index 0000000000..9a11dba923 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/DefaultTokenDetailsDeepLinkHandler.kt @@ -0,0 +1,88 @@ +package com.tangem.feature.tokendetails.deeplink + +import arrow.core.getOrElse +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.domain.notifications.models.NotificationType +import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +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 DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor( + @Assisted private val scope: CoroutineScope, + @Assisted private val queryParams: Map, + private val appRouter: AppRouter, + private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, +) : TokenDetailsDeepLinkHandler { + + init { + handleDeepLink() + } + + private fun handleDeepLink() { + // It is okay here, we are navigating from outside, and there is no other way to getting UserWallet + val userWalletId = queryParams[WALLET_ID_KEY]?.let(::UserWalletId) + ?: getSelectedWalletSyncUseCase().getOrNull()?.walletId + + if (userWalletId == null) { + Timber.e("Error on getting user wallet") + return + } + + val networkId = queryParams[NETWORK_ID_KEY] + val tokenId = queryParams[TOKEN_ID_KEY] + val type = NotificationType.getType(queryParams[TYPE_KEY]) + + if (type == NotificationType.Promo) { + scope.launch { + val cryptoCurrency = getCryptoCurrenciesUseCase(userWalletId = userWalletId).getOrElse { + Timber.e("Error on getting crypto currency list") + return@launch + }.firstOrNull { + it.id.rawNetworkId == networkId && it.id.rawCurrencyId?.value == tokenId + } + + if (cryptoCurrency == null) { + Timber.e( + """ + Could not get crypto currency for + |- $NETWORK_ID_KEY: $networkId + |- $TOKEN_ID_KEY: $tokenId + """.trimIndent(), + ) + return@launch + } + + appRouter.push( + AppRoute.CurrencyDetails( + userWalletId = userWalletId, + currency = cryptoCurrency, + ), + ) + } + } + } + + @AssistedFactory + interface Factory : TokenDetailsDeepLinkHandler.Factory { + override fun create( + coroutineScope: CoroutineScope, + queryParams: Map, + ): DefaultTokenDetailsDeepLinkHandler + } + + private companion object { + const val WALLET_ID_KEY = "walletId" + const val NETWORK_ID_KEY = "network_id" + const val TYPE_KEY = "type" + const val TOKEN_ID_KEY = "token_id" + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/di/TokenDetailsDeepLinkModule.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/di/TokenDetailsDeepLinkModule.kt new file mode 100644 index 0000000000..0b2e70f86e --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/deeplink/di/TokenDetailsDeepLinkModule.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.tokendetails.deeplink.di + +import com.tangem.feature.tokendetails.deeplink.DefaultTokenDetailsDeepLinkHandler +import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TokenDetailsDeepLinkModule { + + @Binds + @Singleton + fun bindWalletDeepLinkHandlerFactory( + impl: DefaultTokenDetailsDeepLinkHandler.Factory, + ): TokenDetailsDeepLinkHandler.Factory +} \ No newline at end of file diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/deeplink/WalletDeepLinkHandler.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/deeplink/WalletDeepLinkHandler.kt new file mode 100644 index 0000000000..b798fcf1e4 --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/deeplink/WalletDeepLinkHandler.kt @@ -0,0 +1,8 @@ +package com.tangem.features.wallet.deeplink + +interface WalletDeepLinkHandler { + + interface Factory { + fun create(): WalletDeepLinkHandler + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultWalletDeepLinkHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultWalletDeepLinkHandler.kt new file mode 100644 index 0000000000..508333060f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultWalletDeepLinkHandler.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.wallet.deeplink + +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultWalletDeepLinkHandler @AssistedInject constructor() : WalletDeepLinkHandler { + + @AssistedFactory + interface Factory : WalletDeepLinkHandler.Factory { + override fun create(): DefaultWalletDeepLinkHandler + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/di/WalletDeepLinkModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/di/WalletDeepLinkModule.kt new file mode 100644 index 0000000000..5b8b66deed --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/di/WalletDeepLinkModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.wallet.deeplink.di + +import com.tangem.feature.wallet.deeplink.DefaultWalletDeepLinkHandler +import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface WalletDeepLinkModule { + + @Binds + @Singleton + fun bindWalletDeepLinkHandlerFactory(impl: DefaultWalletDeepLinkHandler.Factory): WalletDeepLinkHandler.Factory +} \ No newline at end of file