Updated on 2026-08-14
This commit is contained in:
parent
6fdbff6fa8
commit
2091e5eecd
3 changed files with 118 additions and 0 deletions
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.common.routing.deeplink
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.DeepLinkRoute
|
||||
import com.tangem.common.routing.DeepLinkScheme
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Classification of a marketing-banner deeplink used to decide how a banner tap is routed.
|
||||
*
|
||||
* Only the `tangem://` scheme with a swap/buy host triggers contextual in-app routing; everything
|
||||
* else (external `https://` T&S links, unknown hosts, malformed input) is [EXTERNAL] and handed off
|
||||
* to the generic deeplink launcher. This mirrors iOS `DefaultIncomingLinkParser`, where
|
||||
* `https://tangem.com/...` links always resolve to an external link, not an in-app destination.
|
||||
*/
|
||||
enum class MarketingDeeplink {
|
||||
/** `tangem://swap` — open swap for the current token. */
|
||||
SWAP,
|
||||
|
||||
/** `tangem://buy` — open onramp for the current token. */
|
||||
BUY,
|
||||
|
||||
/** External or unrecognized link — route through the generic deeplink launcher. */
|
||||
EXTERNAL,
|
||||
}
|
||||
|
||||
// TODO: [temporary] Banner taps are intercepted in-host and mapped to an AppRoute directly because the
|
||||
// shared tangem://swap and tangem://buy deeplinks open context-less screens (generic swap / BuyCrypto
|
||||
// hub) with no current-token prefill. Replace with targeted swap/onramp deeplinks and drop this
|
||||
// interception: [REDACTED_JIRA]
|
||||
/**
|
||||
* Resolves a marketing-banner [link] into a [MarketingDeeplink]. Never throws: malformed input
|
||||
* degrades to [MarketingDeeplink.EXTERNAL].
|
||||
*/
|
||||
fun resolveMarketingDeeplink(link: String): MarketingDeeplink {
|
||||
val uri = runCatching { URI(link) }.getOrNull() ?: return MarketingDeeplink.EXTERNAL
|
||||
|
||||
if (!uri.scheme.equals(DeepLinkScheme.Tangem.scheme, ignoreCase = true)) {
|
||||
return MarketingDeeplink.EXTERNAL
|
||||
}
|
||||
|
||||
return when (uri.host) {
|
||||
DeepLinkRoute.Swap.host -> MarketingDeeplink.SWAP
|
||||
DeepLinkRoute.Buy.host -> MarketingDeeplink.BUY
|
||||
else -> MarketingDeeplink.EXTERNAL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the contextual in-app route for a marketing-banner deeplink on a token-scoped screen (staking,
|
||||
* yield, swap, onramp): swap for the current token, or onramp to buy it. Returns `null` for
|
||||
* [MarketingDeeplink.EXTERNAL] so the caller falls back to the generic deeplink launcher.
|
||||
*/
|
||||
fun MarketingDeeplink.toContextualRoute(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
screenSource: AnalyticsParam.ScreensSources,
|
||||
onrampSource: OnrampSource = OnrampSource.MARKETING_BANNER,
|
||||
): AppRoute? = when (this) {
|
||||
MarketingDeeplink.SWAP -> AppRoute.Swap(
|
||||
userWalletId = userWalletId,
|
||||
fromCryptoCurrency = currency,
|
||||
screenSource = screenSource.value,
|
||||
)
|
||||
MarketingDeeplink.BUY -> AppRoute.Onramp(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
source = onrampSource,
|
||||
)
|
||||
MarketingDeeplink.EXTERNAL -> null
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.common.routing.deeplink
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class MarketingDeeplinkTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun resolveMarketingDeeplink(model: ResolveModel) {
|
||||
// Act
|
||||
val actual = resolveMarketingDeeplink(model.link)
|
||||
|
||||
// Assert
|
||||
assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
internal data class ResolveModel(val link: String, val expected: MarketingDeeplink)
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// tangem:// swap/buy -> contextual
|
||||
ResolveModel(link = "tangem://swap", expected = MarketingDeeplink.SWAP),
|
||||
ResolveModel(link = "tangem://buy", expected = MarketingDeeplink.BUY),
|
||||
ResolveModel(link = "tangem://swap?foo=bar", expected = MarketingDeeplink.SWAP),
|
||||
ResolveModel(link = "tangem://buy/extra", expected = MarketingDeeplink.BUY),
|
||||
ResolveModel(link = "TANGEM://swap", expected = MarketingDeeplink.SWAP),
|
||||
// tangem:// other hosts -> external
|
||||
ResolveModel(link = "tangem://token", expected = MarketingDeeplink.EXTERNAL),
|
||||
ResolveModel(link = "tangem://promo", expected = MarketingDeeplink.EXTERNAL),
|
||||
// https T&S links -> external (iOS treats these as .link too)
|
||||
ResolveModel(link = "https://tangem.com/swap", expected = MarketingDeeplink.EXTERNAL),
|
||||
ResolveModel(link = "https://tangem.com/buy", expected = MarketingDeeplink.EXTERNAL),
|
||||
ResolveModel(link = "https://tangem.com/promo/summer", expected = MarketingDeeplink.EXTERNAL),
|
||||
// other schemes / garbage -> external
|
||||
ResolveModel(link = "wc://connect", expected = MarketingDeeplink.EXTERNAL),
|
||||
ResolveModel(link = "not a uri", expected = MarketingDeeplink.EXTERNAL),
|
||||
ResolveModel(link = "", expected = MarketingDeeplink.EXTERNAL),
|
||||
)
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ enum class OnrampSource(val analyticsName: String) {
|
|||
TOKEN_LONG_TAP("Long Tap"),
|
||||
TOKEN_DETAILS("Token"),
|
||||
MARKETS("Markets"),
|
||||
MARKETING_BANNER("Marketing Banner"),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue