Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-04 19:10:53 +03:00
parent 8509912947
commit 32983333c3
14 changed files with 98 additions and 22 deletions

View file

@ -281,7 +281,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
sendStakingUnsubmittedHashes()
checkGoogleServicesAvailability()
if (intent != null) {
if (intent != null && savedInstanceState == null) {
// handle intent only on start, not on recreate
deepLinksRegistry.launch(intent)
}

View file

@ -4,7 +4,7 @@ import com.tangem.core.deeplink.DeepLink
class BuyCurrencyDeepLink(
val onReceive: (externalTxId: String) -> Unit,
) : DeepLink {
) : DeepLink() {
override val uri = BUY_REDIRECT_DEEPLINK

View file

@ -2,7 +2,10 @@ package com.tangem.core.deeplink.global
import com.tangem.core.deeplink.DeepLink
class SellCurrencyDeepLink(val onReceive: (data: Data) -> Unit) : DeepLink {
class SellCurrencyDeepLink(
val onReceive: (data: Data) -> Unit,
shouldHandleDelayed: Boolean,
) : DeepLink(shouldHandleDelayed) {
override val uri: String = "tangem://redirect_sell"

View file

@ -3,7 +3,7 @@ package com.tangem.core.deeplink
/**
* Represents a deep link.
*/
interface DeepLink {
abstract class DeepLink(val shouldHandleDelayed: Boolean = false) {
/**
* ID of the deep link.
@ -25,12 +25,12 @@ interface DeepLink {
* "tangem://link/{param1}/{param2}" // With path parameters
* ```
* */
val uri: String
abstract val uri: String
/**
* Method to be called when this deep link is received.
*
* @param params Map of parameters received from the deep link.
* */
fun onReceive(params: Map<String, String>)
abstract fun onReceive(params: Map<String, String>)
}

View file

@ -60,4 +60,12 @@ interface DeepLinksRegistry {
* Registers the [deepLinks] and ensures that they are unregistered when the [ViewModel] is closed.
*/
fun registerWithViewModel(viewModel: ViewModel, deepLinks: Collection<DeepLink>)
/**
* Triggers run last launched [Intent] with deeplink handlers that can handle delayed deeplink
* after handle [Intent] clear that and second time no intent will be handled
*/
fun triggerDelayedDeeplink()
fun cancelDelayedDeeplink()
}

View file

@ -13,8 +13,10 @@ import timber.log.Timber
internal class DefaultDeepLinksRegistry : DeepLinksRegistry {
private var registries: List<DeepLink> = emptyList()
private var lastIntent: Intent? = null
override fun launch(intent: Intent): Boolean {
lastIntent = intent
val received = intent.data ?: return false
var hasMatch = false
@ -33,25 +35,14 @@ internal class DefaultDeepLinksRegistry : DeepLinksRegistry {
val params = getParams(expected, received)
Timber.i(
"""
Matched deep link
|- Expected URI: $expected
|- Received URI: $received
|- Params: $params
""".trimIndent(),
)
logMatch(hasMatch, expected, received, params)
deepLink.onReceive(params)
lastIntent = null // clear intent if it was handled
}
if (!hasMatch) {
Timber.i(
"""
No match found for deep link
|- Received URI: $received
|- Registries: $registries
""".trimIndent(),
)
logMatch(hasMatch, null, received, null)
}
return hasMatch
@ -125,6 +116,54 @@ internal class DefaultDeepLinksRegistry : DeepLinksRegistry {
register(deepLinks)
}
override fun triggerDelayedDeeplink() {
if (lastIntent != null) {
val intent = lastIntent
val received = intent?.data ?: return
var hasMatch = false
registries.forEach { deepLink ->
if (!deepLink.shouldHandleDelayed) return@forEach
val expected = deepLink.uri.toUri()
if (!isMatches(expected, received)) return@forEach
hasMatch = true
val params = getParams(expected, received)
logMatch(hasMatch, expected, received, params)
deepLink.onReceive(params)
}
if (!hasMatch) {
logMatch(hasMatch, null, received, null)
}
lastIntent = null // clear intent in any case handle or not
}
}
override fun cancelDelayedDeeplink() {
lastIntent = null
}
private fun logMatch(hasMatch: Boolean, expected: Uri?, received: Uri?, params: Map<String, String>?) {
if (hasMatch) {
Timber.i(
"""
Matched deep link
|- Expected URI: $expected
|- Received URI: $received
|- Params: $params
""".trimIndent(),
)
} else {
Timber.i(
"""
No match found for deep link
|- Received URI: $received
|- Registries: $registries
""".trimIndent(),
)
}
}
private fun isMatches(received: Uri, expected: Uri): Boolean {
if (received == expected) return true
if (received.authority != expected.authority ||

View file

@ -37,7 +37,7 @@ internal class WalletDeepLinksHandler @Inject constructor(
getDeepLinks(userWallet, viewModel.viewModelScope)
}
deepLinksRegistry.unregisterByIds(deepLinks.map { it.id })
deepLinksRegistry.register(deepLinks)
deepLinksRegistry.register(deepLinks = deepLinks)
viewModel.addCloseable {
deepLinksRegistry.unregister(deepLinks)
@ -51,6 +51,7 @@ internal class WalletDeepLinksHandler @Inject constructor(
onSellCurrencyDeepLink(userWallet, data)
}
},
shouldHandleDelayed = true,
)
return buildList {

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
@ -30,6 +31,7 @@ internal class MultiWalletContentLoader(
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -44,6 +46,7 @@ internal class MultiWalletContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
),
MultiWalletWarningsSubscriber(
userWallet = userWallet,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
@ -28,6 +29,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
@ -44,6 +46,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
@ -28,6 +29,7 @@ internal class SingleWalletWithTokenContentLoader(
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -41,6 +43,7 @@ internal class SingleWalletWithTokenContentLoader(
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
),
MultiWalletWarningsSubscriber(
userWallet = userWallet,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
@ -25,6 +26,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
@ -40,6 +42,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import arrow.core.getOrElse
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
@ -35,6 +36,7 @@ internal abstract class BasicTokenListSubscriber(
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletSubscriber() {
private val sendAnalyticsJobHolder = JobHolder()
@ -111,6 +113,9 @@ internal abstract class BasicTokenListSubscriber(
protected open suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
/* no-op */
if (maybeTokenList.getOrNull(false) != null) {
deepLinksRegistry.triggerDelayedDeeplink()
}
}
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
@ -28,6 +29,7 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
deepLinksRegistry: DeepLinksRegistry,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -36,6 +38,7 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
@ -46,6 +49,7 @@ internal class MultiWalletTokenListSubscriber(
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
updateSortingIfNeeded(maybeTokenList)
super.onTokenListReceived(maybeTokenList)
}
private suspend fun updateSortingIfNeeded(maybeTokenList: Lce<*, TokenList>) {

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
@ -23,6 +24,7 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
deepLinksRegistry: DeepLinksRegistry,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -31,6 +33,7 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {