Updated on 2026-08-14
This commit is contained in:
parent
a6bbcf1e10
commit
0de7027fbc
9 changed files with 154 additions and 4 deletions
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TokenDetailsDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(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)
|
||||
|
||||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue