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_LONG_TAP("Long Tap"),
|
||||||
TOKEN_DETAILS("Token"),
|
TOKEN_DETAILS("Token"),
|
||||||
MARKETS("Markets"),
|
MARKETS("Markets"),
|
||||||
|
MARKETING_BANNER("Marketing Banner"),
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue