Updated on 2026-08-14
This commit is contained in:
parent
ab27ff4bea
commit
2f7c994a61
15 changed files with 256 additions and 137 deletions
|
|
@ -34,6 +34,7 @@
|
|||
<application
|
||||
android:name="com.tangem.tap.TangemHiltApplication"
|
||||
android:allowBackup="false"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
@ -43,7 +44,6 @@
|
|||
android:resizeableActivity="@bool/resizeable_activity"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:allowBackup, android:fullBackupContent, android:label">
|
||||
|
||||
|
|
@ -208,6 +208,39 @@
|
|||
android:host="token_chart"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="buy"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="sell"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="swap"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Disable android.startup completely. Used for Worker according doc -->
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import android.net.Uri
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.DeepLinkRoute
|
||||
import com.tangem.common.routing.DeepLinkScheme
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
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.onramp.deeplink.*
|
||||
import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler
|
||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
|
||||
|
|
@ -21,6 +21,7 @@ import dagger.hilt.android.scopes.ActivityScoped
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import timber.log.Timber
|
||||
|
|
@ -29,9 +30,10 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
@ActivityScoped
|
||||
internal class DeepLinkFactory @Inject constructor(
|
||||
private val cardSdkProvider: CardSdkProvider,
|
||||
private val onrampDeepLink: OnrampDeepLinkHandler.Factory,
|
||||
private val sellDeepLink: SellDeepLinkHandler.Factory,
|
||||
private val buyDeepLink: BuyDeepLinkHandler.Factory,
|
||||
private val sellRedirectDeepLink: SellRedirectDeepLinkHandler.Factory,
|
||||
private val buyRedirectDeepLink: BuyRedirectDeepLinkHandler.Factory,
|
||||
private val referralDeepLink: ReferralDeepLinkHandler.Factory,
|
||||
private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory,
|
||||
private val walletDeepLink: WalletDeepLinkHandler.Factory,
|
||||
|
|
@ -39,6 +41,9 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
private val stakingDeepLink: StakingDeepLinkHandler.Factory,
|
||||
private val marketsDeepLink: MarketsDeepLinkHandler.Factory,
|
||||
private val marketsTokenDetailDeepLink: MarketsTokenDetailDeepLinkHandler.Factory,
|
||||
private val buyDeepLink: BuyDeepLinkHandler.Factory,
|
||||
private val sellDeepLink: SellDeepLinkHandler.Factory,
|
||||
private val swapDeepLink: SwapDeepLinkHandler.Factory,
|
||||
) {
|
||||
private val permittedAppRoute = MutableStateFlow(false)
|
||||
|
||||
|
|
@ -55,15 +60,19 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
|- Received URI: $deeplinkUri
|
||||
""".trimIndent(),
|
||||
)
|
||||
permittedAppRoute
|
||||
.transformLatest<Boolean, Unit> { isPermitted ->
|
||||
if (isPermitted) {
|
||||
lastDeepLink?.let {
|
||||
launchDeepLink(it, coroutineScope, isFromOnNewIntent)
|
||||
}
|
||||
lastDeepLink = null
|
||||
combine(
|
||||
permittedAppRoute,
|
||||
cardSdkProvider.sdk.uiVisibility(),
|
||||
) { isRoutePermitted, isCardSdkVisible ->
|
||||
isRoutePermitted to isCardSdkVisible
|
||||
}.transformLatest<Pair<Boolean, Boolean>, Unit> { (isRoutePermitted, isCardSdkVisible) ->
|
||||
if (isRoutePermitted && !isCardSdkVisible) {
|
||||
lastDeepLink?.let {
|
||||
launchDeepLink(it, coroutineScope, isFromOnNewIntent)
|
||||
}
|
||||
lastDeepLink = null
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
.saveIn(deepLinkHandlerJobHolder)
|
||||
}
|
||||
|
|
@ -103,8 +112,8 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
val queryParams = getQueryParams(deeplinkUri)
|
||||
when (deeplinkUri.host) {
|
||||
DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.Sell.host -> sellDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.Buy.host -> buyDeepLink.create(coroutineScope)
|
||||
DeepLinkRoute.SellRedirect.host -> sellRedirectDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.BuyRedirect.host -> buyRedirectDeepLink.create(coroutineScope)
|
||||
DeepLinkRoute.Referral.host -> referralDeepLink.create()
|
||||
DeepLinkRoute.Wallet.host -> walletDeepLink.create()
|
||||
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(
|
||||
|
|
@ -115,6 +124,9 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.Markets.host -> marketsDeepLink.create()
|
||||
DeepLinkRoute.MarketTokenDetail.host -> marketsTokenDetailDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.Buy.host -> buyDeepLink.create()
|
||||
DeepLinkRoute.Sell.host -> sellDeepLink.create()
|
||||
DeepLinkRoute.Swap.host -> swapDeepLink.create()
|
||||
else -> {
|
||||
Timber.i(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package com.tangem.tap.routing.utils
|
|||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
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.onramp.deeplink.*
|
||||
import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler
|
||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
|
||||
|
|
@ -18,6 +18,7 @@ import io.mockk.verify
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.*
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
|
|
@ -30,10 +31,10 @@ class DeepLinkFactoryTest {
|
|||
private val onrampDeepLinkFactory = mockk<OnrampDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val sellDeepLinkFactory = mockk<SellDeepLinkHandler.Factory>(relaxed = true) {
|
||||
private val sellRedirectDeepLinkFactory = mockk<SellRedirectDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val buyDeepLinkFactory = mockk<BuyDeepLinkHandler.Factory>(relaxed = true) {
|
||||
private val buyRedirectDeepLinkFactory = mockk<BuyRedirectDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any()) } returns mockk()
|
||||
}
|
||||
private val referralDeepLinkFactory = mockk<ReferralDeepLinkHandler.Factory>(relaxed = true) {
|
||||
|
|
@ -57,7 +58,19 @@ class DeepLinkFactoryTest {
|
|||
private val marketsTokenDetailDeepLinkFactory = mockk<MarketsTokenDetailDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val sellDeepLinkFactory = mockk<SellDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
private val buyDeepLinkFactory = mockk<BuyDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
private val swapDeepLinkFactory = mockk<SwapDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
|
||||
private val cardSdkProvider = mockk<CardSdkProvider>(relaxed = true) {
|
||||
every { sdk.uiVisibility() } returns MutableStateFlow(false)
|
||||
}
|
||||
private val mockedUri = mockk<Uri>(relaxed = true)
|
||||
private val isFromOnNewIntent: Boolean = false
|
||||
|
||||
|
|
@ -65,16 +78,20 @@ class DeepLinkFactoryTest {
|
|||
private lateinit var testScope: TestScope
|
||||
|
||||
private val deepLinkFactory = DeepLinkFactory(
|
||||
onrampDeepLinkFactory,
|
||||
sellDeepLinkFactory,
|
||||
buyDeepLinkFactory,
|
||||
referralDeepLinkFactory,
|
||||
walletConnectDeepLinkFactory,
|
||||
walletDeepLinkFactory,
|
||||
tokenDetailsDeepLinkFactory,
|
||||
stakingDeepLinkFactory,
|
||||
marketsDeepLinkFactory,
|
||||
marketsTokenDetailDeepLinkFactory,
|
||||
cardSdkProvider = cardSdkProvider,
|
||||
onrampDeepLink = onrampDeepLinkFactory,
|
||||
sellRedirectDeepLink = sellRedirectDeepLinkFactory,
|
||||
buyRedirectDeepLink = buyRedirectDeepLinkFactory,
|
||||
referralDeepLink = referralDeepLinkFactory,
|
||||
walletConnectDeepLink = walletConnectDeepLinkFactory,
|
||||
walletDeepLink = walletDeepLinkFactory,
|
||||
tokenDetailsDeepLink = tokenDetailsDeepLinkFactory,
|
||||
stakingDeepLink = stakingDeepLinkFactory,
|
||||
marketsDeepLink = marketsDeepLinkFactory,
|
||||
marketsTokenDetailDeepLink = marketsTokenDetailDeepLinkFactory,
|
||||
buyDeepLink = buyDeepLinkFactory,
|
||||
sellDeepLink = sellDeepLinkFactory,
|
||||
swapDeepLink = swapDeepLinkFactory,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -140,6 +157,24 @@ class DeepLinkFactoryTest {
|
|||
verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleDeeplink does not launch when card scan visible`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
every { mockedUri.host } returns "onramp"
|
||||
every { mockedUri.query } returns "param=value"
|
||||
every { mockedUri.queryParameterNames } returns setOf("param")
|
||||
every { mockedUri.getQueryParameter("param") } returns "value"
|
||||
every { cardSdkProvider.sdk.uiVisibility() } returns MutableStateFlow(true)
|
||||
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
// Verify no handler was called
|
||||
verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `launchDeepLink handles tangem scheme correctly`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
|
|
@ -184,8 +219,8 @@ class DeepLinkFactoryTest {
|
|||
|
||||
verify(inverse = true) {
|
||||
onrampDeepLinkFactory.create(any(), any())
|
||||
sellDeepLinkFactory.create(any(), any())
|
||||
buyDeepLinkFactory.create(any())
|
||||
sellRedirectDeepLinkFactory.create(any(), any())
|
||||
buyRedirectDeepLinkFactory.create(any())
|
||||
referralDeepLinkFactory.create()
|
||||
walletConnectDeepLinkFactory.create(any())
|
||||
walletDeepLinkFactory.create()
|
||||
|
|
@ -208,11 +243,11 @@ class DeepLinkFactoryTest {
|
|||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||
|
||||
// Test Sell
|
||||
// Test Sell Redirect
|
||||
every { mockedUri.host } returns "redirect_sell"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||
verify { sellRedirectDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||
|
||||
// Test Token Details
|
||||
every { mockedUri.host } returns "token"
|
||||
|
|
@ -242,11 +277,11 @@ class DeepLinkFactoryTest {
|
|||
every { mockedUri.queryParameterNames } returns emptySet()
|
||||
every { mockedUri.getQueryParameter(any()) } returns ""
|
||||
|
||||
// Test Buy
|
||||
// Test Buy Redirect
|
||||
every { mockedUri.host } returns "redirect"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { buyDeepLinkFactory.create(eq(testScope)) }
|
||||
verify { buyRedirectDeepLinkFactory.create(eq(testScope)) }
|
||||
|
||||
// Test Referral
|
||||
every { mockedUri.host } returns "referral"
|
||||
|
|
@ -265,6 +300,24 @@ class DeepLinkFactoryTest {
|
|||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { marketsDeepLinkFactory.create() }
|
||||
|
||||
// Test Sell
|
||||
every { mockedUri.host } returns "sell"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { sellDeepLinkFactory.create() }
|
||||
|
||||
// Test Swap
|
||||
every { mockedUri.host } returns "swap"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { sellDeepLinkFactory.create() }
|
||||
|
||||
// Test Buy
|
||||
every { mockedUri.host } returns "buy"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { buyDeepLinkFactory.create() }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -279,12 +332,15 @@ class DeepLinkFactoryTest {
|
|||
|
||||
verify(inverse = true) {
|
||||
onrampDeepLinkFactory.create(any(), any())
|
||||
sellDeepLinkFactory.create(any(), any())
|
||||
buyDeepLinkFactory.create(any())
|
||||
sellRedirectDeepLinkFactory.create(any(), any())
|
||||
buyRedirectDeepLinkFactory.create(any())
|
||||
referralDeepLinkFactory.create()
|
||||
walletConnectDeepLinkFactory.create(any())
|
||||
walletDeepLinkFactory.create()
|
||||
tokenDetailsDeepLinkFactory.create(any(), any(), any())
|
||||
buyDeepLinkFactory.create()
|
||||
sellDeepLinkFactory.create()
|
||||
swapDeepLinkFactory.create()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue