Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-30 19:28:40 +05:00
parent fc73f08b5e
commit c763f84aad
9 changed files with 202 additions and 7 deletions

View file

@ -186,6 +186,28 @@
android:host="staking"
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="markets"
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="token_chart"
android:scheme="tangem" />
</intent-filter>
</activity>
<!-- Disable android.startup completely. Used for Worker according doc -->

View file

@ -5,6 +5,8 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.DeepLinkRoute
import com.tangem.common.routing.DeepLinkScheme
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
@ -35,6 +37,8 @@ internal class DeepLinkFactory @Inject constructor(
private val walletDeepLink: WalletDeepLinkHandler.Factory,
private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory,
private val stakingDeepLink: StakingDeepLinkHandler.Factory,
private val marketsDeepLink: MarketsDeepLinkHandler.Factory,
private val marketsTokenDetailDeepLink: MarketsTokenDetailDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@ -109,6 +113,8 @@ internal class DeepLinkFactory @Inject constructor(
isFromOnNewIntent = isFromOnNewIntent,
)
DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.Markets.host -> marketsDeepLink.create()
DeepLinkRoute.MarketTokenDetail.host -> marketsTokenDetailDeepLink.create(coroutineScope, queryParams)
else -> {
Timber.i(
"""

View file

@ -3,6 +3,8 @@ package com.tangem.tap.routing.utils
import android.net.Uri
import com.tangem.common.routing.AppRoute
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
@ -49,6 +51,12 @@ class DeepLinkFactoryTest {
private val stakingDeepLinkFactory = mockk<StakingDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
private val marketsDeepLinkFactory = mockk<MarketsDeepLinkHandler.Factory>(relaxed = true) {
every { create() } returns mockk()
}
private val marketsTokenDetailDeepLinkFactory = mockk<MarketsTokenDetailDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
private val mockedUri = mockk<Uri>(relaxed = true)
private val isFromOnNewIntent: Boolean = false
@ -65,6 +73,8 @@ class DeepLinkFactoryTest {
walletDeepLinkFactory,
tokenDetailsDeepLinkFactory,
stakingDeepLinkFactory,
marketsDeepLinkFactory,
marketsTokenDetailDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@ -220,13 +230,13 @@ class DeepLinkFactoryTest {
every { mockedUri.host } returns "staking"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify {
tokenDetailsDeepLinkFactory.create(
eq(testScope),
eq(mapOf("param" to "value")),
eq(isFromOnNewIntent),
)
}
verify { stakingDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
// Test Market Token Detail
every { mockedUri.host } returns "token_chart"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { marketsTokenDetailDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
// Reset params
every { mockedUri.queryParameterNames } returns emptySet()
@ -249,6 +259,12 @@ class DeepLinkFactoryTest {
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { walletDeepLinkFactory.create() }
// Test Markets
every { mockedUri.host } returns "markets"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { marketsDeepLinkFactory.create() }
}
@Test

View file

@ -31,6 +31,14 @@ sealed class DeepLinkRoute {
data object Staking : DeepLinkRoute() {
override val host: String = "staking"
}
data object Markets : DeepLinkRoute() {
override val host: String = "markets"
}
data object MarketTokenDetail : DeepLinkRoute() {
override val host: String = "token_chart"
}
}
enum class DeepLinkScheme(val scheme: String) {

View file

@ -0,0 +1,8 @@
package com.tangem.features.markets.deeplink
interface MarketsDeepLinkHandler {
interface Factory {
fun create(): MarketsDeepLinkHandler
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.markets.deeplink
import kotlinx.coroutines.CoroutineScope
interface MarketsTokenDetailDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope, params: Map<String, String>): MarketsTokenDetailDeepLinkHandler
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultMarketsDeepLinkHandler @AssistedInject constructor(
appRouter: AppRouter,
) : MarketsDeepLinkHandler {
init {
appRouter.push(AppRoute.Markets)
}
@AssistedFactory
interface Factory : MarketsDeepLinkHandler.Factory {
override fun create(): DefaultMarketsDeepLinkHandler
}
}

View file

@ -0,0 +1,79 @@
package com.tangem.features.markets.deeplink
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrency
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 DefaultMarketsTokenDetailDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
@Assisted queryParams: Map<String, String>,
appRouter: AppRouter,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
) : MarketsTokenDetailDeepLinkHandler {
init {
val tokenId = queryParams[TOKEN_ID_KEY]
val rawTokenId = CryptoCurrency.RawID(tokenId.orEmpty())
scope.launch {
val appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse {
AppCurrency.Default
}
val tokenInfo = getTokenMarketInfoUseCase(
appCurrency = appCurrency,
tokenId = rawTokenId,
tokenSymbol = TOKEN_SYMBOL_KEY,
).getOrElse {
Timber.e("Failed to get market token info")
return@launch
}
appRouter.push(
AppRoute.MarketsTokenDetails(
token = TokenMarketParams(
id = rawTokenId,
name = tokenInfo.name,
symbol = tokenInfo.symbol,
tokenQuotes = TokenMarketParams.Quotes(
currentPrice = tokenInfo.quotes.currentPrice,
h24Percent = tokenInfo.quotes.h24ChangePercent,
weekPercent = tokenInfo.quotes.weekChangePercent,
monthPercent = tokenInfo.quotes.monthChangePercent,
),
imageUrl = getTokenIconUrlFromDefaultHost(rawTokenId),
),
appCurrency = appCurrency,
showPortfolio = true,
analyticsParams = null,
),
)
}
}
@AssistedFactory
interface Factory : MarketsTokenDetailDeepLinkHandler.Factory {
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
): DefaultMarketsTokenDetailDeepLinkHandler
}
private companion object {
const val TOKEN_ID_KEY = "token_id"
const val TOKEN_SYMBOL_KEY = "token_symbol"
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.features.markets.deeplink.di
import com.tangem.features.markets.deeplink.DefaultMarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.DefaultMarketsTokenDetailDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
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 MarketsDeepLinkModule {
@Binds
@Singleton
fun bindMarketsDeepLinkHandlerFactory(impl: DefaultMarketsDeepLinkHandler.Factory): MarketsDeepLinkHandler.Factory
@Binds
@Singleton
fun bindMarketsTokenDetailDeepLinkHandlerFactory(
impl: DefaultMarketsTokenDetailDeepLinkHandler.Factory,
): MarketsTokenDetailDeepLinkHandler.Factory
}