Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-30 14:24:44 +05:00
commit e33bd4d849
13 changed files with 255 additions and 50 deletions

View file

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

View file

@ -33,7 +33,9 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.deeplink.DEEPLINK_KEY
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.WEBLINK_KEY
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.UiDependencies
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.data.card.sdk.CardSdkOwner
@ -74,6 +76,7 @@ import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.utils.DeepLinkFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.utils.extensions.uriValidate
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@ -185,6 +188,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
internal lateinit var walletConnectFeatureToggles: WalletConnectFeatureToggles
@Inject
internal lateinit var urlOpener: UrlOpener
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -239,7 +245,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) {
// handle intent only on start, not on recreate
handleDeepLink(intent)
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
lifecycle.addObserver(WindowObscurationObserver)
@ -391,7 +397,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
if (intent != null) {
handleDeepLink(intent)
handleDeepLink(intent = intent, isFromOnNewIntent = true)
}
}
@ -473,18 +479,31 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) {
handleDeepLink(intent)
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
viewModel.checkForUnfinishedBackup()
}
private fun handleDeepLink(intent: Intent) {
private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) {
if (routingFeatureToggle.isDeepLinkNavigationEnabled) {
val deepLinkExtras = intent.getStringExtra(DEEPLINK_KEY)?.toUri()
val receivedDeepLink = intent.data ?: deepLinkExtras ?: return
val webLink = intent.getStringExtra(WEBLINK_KEY)
deeplinkFactory.handleDeeplink(deeplinkUri = receivedDeepLink, coroutineScope = lifecycleScope)
val receivedDeepLink = intent.data ?: deepLinkExtras
when {
receivedDeepLink != null -> {
deeplinkFactory.handleDeeplink(
deeplinkUri = receivedDeepLink,
coroutineScope = lifecycleScope,
isFromOnNewIntent = isFromOnNewIntent,
)
}
webLink?.uriValidate() == true -> {
urlOpener.openUrl(webLink)
}
}
} else {
deepLinksRegistry.launch(intent)
}

View file

@ -16,6 +16,7 @@ import coil.request.ImageRequest
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.tangem.core.deeplink.DEEPLINK_KEY
import com.tangem.core.deeplink.WEBLINK_KEY
import com.tangem.domain.common.LogConfig
import com.tangem.tap.MainActivity
import com.tangem.tap.common.images.createCoilImageLoader
@ -39,6 +40,7 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
val intent = Intent(applicationContext, MainActivity::class.java).apply {
putExtra(DEEPLINK_KEY, message.data[DEEPLINK_KEY])
putExtra(WEBLINK_KEY, message.data[WEBLINK_KEY])
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}

View file

@ -8,11 +8,13 @@ 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.staking.api.deeplink.StakingDeepLinkHandler
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
import com.tangem.utils.extensions.uriValidate
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -32,6 +34,7 @@ internal class DeepLinkFactory @Inject constructor(
private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory,
private val walletDeepLink: WalletDeepLinkHandler.Factory,
private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory,
private val stakingDeepLink: StakingDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@ -39,7 +42,7 @@ internal class DeepLinkFactory @Inject constructor(
private val deepLinkHandlerJobHolder = JobHolder()
@OptIn(ExperimentalCoroutinesApi::class)
fun handleDeeplink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
fun handleDeeplink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
lastDeepLink = deeplinkUri
Timber.i(
@ -52,7 +55,7 @@ internal class DeepLinkFactory @Inject constructor(
.transformLatest<Boolean, Unit> { isPermitted ->
if (isPermitted) {
lastDeepLink?.let {
launchDeepLink(it, coroutineScope)
launchDeepLink(it, coroutineScope, isFromOnNewIntent)
}
lastDeepLink = null
}
@ -77,9 +80,9 @@ internal class DeepLinkFactory @Inject constructor(
}
}
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
when (deeplinkUri.scheme) {
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope)
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope, isFromOnNewIntent)
DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri)
else -> {
Timber.i(
@ -92,7 +95,7 @@ internal class DeepLinkFactory @Inject constructor(
}
}
private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
val queryParams = getQueryParams(deeplinkUri)
when (deeplinkUri.host) {
DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams)
@ -100,7 +103,12 @@ internal class DeepLinkFactory @Inject constructor(
DeepLinkRoute.Buy.host -> buyDeepLink.create(coroutineScope)
DeepLinkRoute.Referral.host -> referralDeepLink.create()
DeepLinkRoute.Wallet.host -> walletDeepLink.create()
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(
coroutineScope = coroutineScope,
queryParams = queryParams,
isFromOnNewIntent = isFromOnNewIntent,
)
DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
else -> {
Timber.i(
"""
@ -119,24 +127,11 @@ internal class DeepLinkFactory @Inject constructor(
uri.queryParameterNames.forEach { paramName ->
val paramValue = uri.getQueryParameter(paramName)
if (paramName.validate() && paramValue?.validate() == true) {
if (paramName.uriValidate() && paramValue?.uriValidate() == true) {
params[paramName] = paramValue
}
}
return params
}
/**
* Check for malicious symbol in uri part
*/
private fun String.validate(): Boolean {
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
return !regex.containsMatchIn(this)
}
private companion object {
const val DEEPLINK_VALIDATION_REGEX = "['\";<>()+\\\\]"
}
}

View file

@ -6,6 +6,7 @@ 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.staking.api.deeplink.StakingDeepLinkHandler
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
@ -43,10 +44,14 @@ class DeepLinkFactoryTest {
every { create() } returns mockk()
}
private val tokenDetailsDeepLinkFactory = mockk<TokenDetailsDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any(), any()) } returns mockk()
}
private val stakingDeepLinkFactory = mockk<StakingDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
private val mockedUri = mockk<Uri>(relaxed = true)
private val isFromOnNewIntent: Boolean = false
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@ -59,6 +64,7 @@ class DeepLinkFactoryTest {
walletConnectDeepLinkFactory,
walletDeepLinkFactory,
tokenDetailsDeepLinkFactory,
stakingDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@ -96,7 +102,7 @@ class DeepLinkFactoryTest {
every { mockedUri.getQueryParameter("param") } returns "value"
// Set permittedAppRoute to true
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
advanceUntilIdle()
@ -115,7 +121,7 @@ class DeepLinkFactoryTest {
every { mockedUri.queryParameterNames } returns setOf("param")
every { mockedUri.getQueryParameter("param") } returns "value"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
deepLinkFactory.checkRoutingReadiness(AppRoute.Initial)
advanceUntilIdle()
@ -133,7 +139,7 @@ class DeepLinkFactoryTest {
every { mockedUri.getQueryParameter("param") } returns "value"
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
@ -148,7 +154,7 @@ class DeepLinkFactoryTest {
every { mockedUri.host } returns ""
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
@ -162,7 +168,7 @@ class DeepLinkFactoryTest {
every { mockedUri.scheme } returns "https"
every { mockedUri.host } returns "example.com"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
@ -173,7 +179,7 @@ class DeepLinkFactoryTest {
referralDeepLinkFactory.create()
walletConnectDeepLinkFactory.create(any())
walletDeepLinkFactory.create()
tokenDetailsDeepLinkFactory.create(any(), any())
tokenDetailsDeepLinkFactory.create(any(), any(), any())
}
}
@ -188,21 +194,39 @@ class DeepLinkFactoryTest {
// Test Onramp
every { mockedUri.host } returns "onramp"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
// Test Sell
every { mockedUri.host } returns "redirect_sell"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
// Test Token Details
every { mockedUri.host } returns "token"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { tokenDetailsDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
verify {
tokenDetailsDeepLinkFactory.create(
eq(testScope),
eq(mapOf("param" to "value")),
eq(isFromOnNewIntent),
)
}
// Test Staking
every { mockedUri.host } returns "staking"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify {
tokenDetailsDeepLinkFactory.create(
eq(testScope),
eq(mapOf("param" to "value")),
eq(isFromOnNewIntent),
)
}
// Reset params
every { mockedUri.queryParameterNames } returns emptySet()
@ -210,19 +234,19 @@ class DeepLinkFactoryTest {
// Test Buy
every { mockedUri.host } returns "redirect"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { buyDeepLinkFactory.create(eq(testScope)) }
// Test Referral
every { mockedUri.host } returns "referral"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { referralDeepLinkFactory.create() }
// Test Wallet
every { mockedUri.host } returns "main"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { walletDeepLinkFactory.create() }
}
@ -233,7 +257,7 @@ class DeepLinkFactoryTest {
every { mockedUri.host } returns "unknown"
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
@ -244,7 +268,7 @@ class DeepLinkFactoryTest {
referralDeepLinkFactory.create()
walletConnectDeepLinkFactory.create(any())
walletDeepLinkFactory.create()
tokenDetailsDeepLinkFactory.create(any(), any())
tokenDetailsDeepLinkFactory.create(any(), any(), any())
}
}
@ -259,7 +283,7 @@ class DeepLinkFactoryTest {
every { mockedUri.getQueryParameter("quote") } returns "O'Brien"
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
@ -275,49 +299,49 @@ class DeepLinkFactoryTest {
every { mockedUri.query } returns "safe=ok"
every { mockedUri.queryParameterNames } returns setOf("safe")
every { mockedUri.getQueryParameter("safe") } returns "ok"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
every { mockedUri.query } returns "param123=ok"
every { mockedUri.queryParameterNames } returns setOf("param123")
every { mockedUri.getQueryParameter("param123") } returns "ok"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param123" to "ok"))) }
every { mockedUri.query } returns "unsafe=<script>"
every { mockedUri.queryParameterNames } returns setOf("unsafe")
every { mockedUri.getQueryParameter("unsafe") } returns "<script>"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
every { mockedUri.query } returns "unsafe=O'Brien"
every { mockedUri.queryParameterNames } returns setOf("unsafe")
every { mockedUri.getQueryParameter("unsafe") } returns "O'Brien"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
every { mockedUri.query } returns "unsafe=test;"
every { mockedUri.queryParameterNames } returns setOf("unsafe")
every { mockedUri.getQueryParameter("unsafe") } returns "test;"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
every { mockedUri.query } returns "unsafe=test+attack"
every { mockedUri.queryParameterNames } returns setOf("unsafe")
every { mockedUri.getQueryParameter("unsafe") } returns "test+attack"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
every { mockedUri.query } returns "unsafe=test\\path"
every { mockedUri.queryParameterNames } returns setOf("unsafe")
every { mockedUri.getQueryParameter("unsafe") } returns "test\\path"
deepLinkFactory.handleDeeplink(mockedUri, testScope)
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
}

View file

@ -27,6 +27,10 @@ sealed class DeepLinkRoute {
data object TokenDetails : DeepLinkRoute() {
override val host: String = "token"
}
data object Staking : DeepLinkRoute() {
override val host: String = "staking"
}
}
enum class DeepLinkScheme(val scheme: String) {

View file

@ -6,6 +6,7 @@ import android.content.Intent
* Key to pass deeplink via intent
*/
const val DEEPLINK_KEY = "deeplink"
const val WEBLINK_KEY = "link"
// TODO: Add tests
/**

View file

@ -0,0 +1,12 @@
package com.tangem.utils.extensions
private const val DEEPLINK_VALIDATION_REGEX = "['\";<>()+\\\\]"
/**
* Check for malicious symbol in uri part
*/
fun String.uriValidate(): Boolean {
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
return !regex.containsMatchIn(this)
}

View file

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

View file

@ -0,0 +1,94 @@
package com.tangem.features.staking.impl.deeplink
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.domain.staking.GetYieldUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
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 DefaultStakingDeepLinkHandler @AssistedInject constructor(
@Assisted private val scope: CoroutineScope,
@Assisted private val queryParams: Map<String, String>,
private val appRouter: AppRouter,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val getYieldUseCase: GetYieldUseCase,
) : StakingDeepLinkHandler {
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]
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
}
val yield = getYieldUseCase.invoke(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
).getOrElse {
error("Staking is unavailable for ${cryptoCurrency.name}")
return@launch
}
appRouter.push(
AppRoute.Staking(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
yieldId = yield.id,
),
)
}
}
@AssistedFactory
interface Factory : StakingDeepLinkHandler.Factory {
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
): DefaultStakingDeepLinkHandler
}
private companion object {
const val WALLET_ID_KEY = "walletId"
const val NETWORK_ID_KEY = "network_id"
const val TOKEN_ID_KEY = "token_id"
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.features.staking.impl.deeplink.di
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
import com.tangem.features.staking.impl.deeplink.DefaultStakingDeepLinkHandler
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 StakingDeepLinkModule {
@Binds
@Singleton
fun bindStakingDeepLinkHandlerFactory(impl: DefaultStakingDeepLinkHandler.Factory): StakingDeepLinkHandler.Factory
}

View file

@ -5,6 +5,10 @@ import kotlinx.coroutines.CoroutineScope
interface TokenDetailsDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope, queryParams: Map<String, String>): TokenDetailsDeepLinkHandler
fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
isFromOnNewIntent: Boolean,
): TokenDetailsDeepLinkHandler
}
}

View file

@ -4,6 +4,7 @@ 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.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -15,12 +16,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
@Suppress("LongParameterList")
internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
@Assisted private val scope: CoroutineScope,
@Assisted private val queryParams: Map<String, String>,
@Assisted private val isFromOnNewIntent: Boolean,
private val appRouter: AppRouter,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
) : TokenDetailsDeepLinkHandler {
init {
@ -67,6 +71,14 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
currency = cryptoCurrency,
),
)
if (isFromOnNewIntent) {
fetchCurrencyStatusUseCase.invoke(
userWalletId = userWalletId,
id = cryptoCurrency.id,
refresh = true,
)
}
}
}
}
@ -76,6 +88,7 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
isFromOnNewIntent: Boolean,
): DefaultTokenDetailsDeepLinkHandler
}