diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 13f1801543..b61add5332 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -176,6 +176,16 @@
android:scheme="tangem" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 8a5266c470..0a6ed669e7 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -76,7 +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.validate
+import com.tangem.utils.extensions.uriValidate
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@@ -496,7 +496,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
receivedDeepLink != null -> {
deeplinkFactory.handleDeeplink(deeplinkUri = receivedDeepLink, coroutineScope = lifecycleScope)
}
- webLink?.validate() == true -> {
+ webLink?.uriValidate() == true -> {
urlOpener.openUrl(webLink)
}
}
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
index 7c69a01577..1e05612cee 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
@@ -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)
@@ -101,6 +104,7 @@ internal class DeepLinkFactory @Inject constructor(
DeepLinkRoute.Referral.host -> referralDeepLink.create()
DeepLinkRoute.Wallet.host -> walletDeepLink.create()
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(coroutineScope, queryParams)
+ DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
else -> {
Timber.i(
"""
@@ -119,7 +123,7 @@ 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
}
}
diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
index 32be06bd2f..f13bfa3ad8 100644
--- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
+++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
@@ -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
@@ -45,6 +46,9 @@ class DeepLinkFactoryTest {
private val tokenDetailsDeepLinkFactory = mockk(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
+ private val stakingDeepLinkFactory = mockk(relaxed = true) {
+ every { create(any(), any()) } returns mockk()
+ }
private val mockedUri = mockk(relaxed = true)
@@ -59,6 +63,7 @@ class DeepLinkFactoryTest {
walletConnectDeepLinkFactory,
walletDeepLinkFactory,
tokenDetailsDeepLinkFactory,
+ stakingDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -204,6 +209,12 @@ class DeepLinkFactoryTest {
advanceUntilIdle()
verify { tokenDetailsDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
+ // Test Staking
+ every { mockedUri.host } returns "staking"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope)
+ advanceUntilIdle()
+ verify { tokenDetailsDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
+
// Reset params
every { mockedUri.queryParameterNames } returns emptySet()
every { mockedUri.getQueryParameter(any()) } returns ""
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
index c683a4136d..3d7be7ec68 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
@@ -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) {
diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt b/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt
index 07624b12c0..5b8f7e3ca4 100644
--- a/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt
+++ b/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt
@@ -5,7 +5,7 @@ private const val DEEPLINK_VALIDATION_REGEX = "['\";<>()+\\\\]"
/**
* Check for malicious symbol in uri part
*/
-fun String.validate(): Boolean {
+fun String.uriValidate(): Boolean {
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
return !regex.containsMatchIn(this)
diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/deeplink/StakingDeepLinkHandler.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/deeplink/StakingDeepLinkHandler.kt
new file mode 100644
index 0000000000..0453140239
--- /dev/null
+++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/deeplink/StakingDeepLinkHandler.kt
@@ -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): StakingDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt
new file mode 100644
index 0000000000..00d124d530
--- /dev/null
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/DefaultStakingDeepLinkHandler.kt
@@ -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,
+ 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,
+ ): DefaultStakingDeepLinkHandler
+ }
+
+ private companion object {
+ const val WALLET_ID_KEY = "walletId"
+ const val NETWORK_ID_KEY = "network_id"
+ const val TOKEN_ID_KEY = "token_id"
+ }
+}
\ No newline at end of file
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/di/StakingDeepLinkModule.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/di/StakingDeepLinkModule.kt
new file mode 100644
index 0000000000..b4ca9dc5b9
--- /dev/null
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/deeplink/di/StakingDeepLinkModule.kt
@@ -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
+}
\ No newline at end of file