Updated on 2026-08-14
This commit is contained in:
parent
2b000c1c72
commit
2415a8a37b
2 changed files with 172 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TRANSACTION_ID_KEY
|
|||
import com.tangem.common.routing.deeplink.DeeplinkConst.TYPE_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -47,6 +48,7 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
) : TokenDetailsDeepLinkHandler {
|
||||
|
||||
init {
|
||||
|
|
@ -81,6 +83,9 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Refresh the portfolio before searching so a token just added on the backend is present locally.
|
||||
refreshAccountsIfNeeded(userWallet)
|
||||
|
||||
val cryptoCurrency = findCryptoCurrency(userWallet = userWallet, networkId = networkId, tokenId = tokenId)
|
||||
|
||||
if (cryptoCurrency == null) {
|
||||
|
|
@ -91,6 +96,8 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
|- $TOKEN_ID_KEY: $tokenId
|
||||
""".trimIndent(),
|
||||
)
|
||||
// Token is not in the response (not indexed yet / backend error): go to main, do not add.
|
||||
appRouter.popTo(AppRoute.Wallet)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +130,21 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes wallet accounts so a token just added on the backend appears in the local portfolio.
|
||||
*
|
||||
* Only when the app was open on push tap ([isFromOnNewIntent]) and the wallet is multi-currency:
|
||||
* on cold start the fresh list is already loaded by the regular auth flow, and single-currency
|
||||
* wallets have a fixed token. The fetch is best-effort — on failure we fall through and try the
|
||||
* current cache, so existing tokens (e.g. swap/onramp pushes) still open without regression.
|
||||
*/
|
||||
private suspend fun refreshAccountsIfNeeded(userWallet: UserWallet) {
|
||||
if (isFromOnNewIntent && userWallet.isMultiCurrency) {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId = userWallet.walletId))
|
||||
.onLeft { TangemLogger.e("Error on refreshing wallet accounts", it) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchCurrency(userWallet: UserWallet, cryptoCurrency: CryptoCurrency) {
|
||||
val isMultiCurrency = userWallet.isMultiCurrency
|
||||
when {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TRANSACTION_ID_KEY
|
|||
import com.tangem.common.routing.deeplink.DeeplinkConst.TYPE_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
|
|
@ -50,6 +51,7 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
private val getUserWalletUseCase: GetUserWalletUseCase = mockk()
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher = mockk()
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
@ -57,6 +59,8 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
mockkObject(TangemLogger)
|
||||
every { analyticsEventHandler.send(any()) } just Runs
|
||||
every { appRouter.push(any(), any()) } just Runs
|
||||
every { appRouter.popTo(route = any(), onComplete = any()) } just Runs
|
||||
coEvery { singleAccountListFetcher.invoke(any()) } returns Either.Right(Unit)
|
||||
val userWallet: UserWallet = mockk()
|
||||
every { userWallet.walletId } returns mockk()
|
||||
every { getSelectedWalletSync() } returns Either.Right(
|
||||
|
|
@ -461,6 +465,151 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multicurrency wallet AND isFromOnNewIntent WHEN handle deeplink THEN refresh wallet accounts`() =
|
||||
runTest {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val cryptoCurrency = mockCryptoCurrency()
|
||||
mockMultiCurrencyWallet(userWalletId)
|
||||
mockSelectWallet(userWalletId)
|
||||
coEvery { singleAccountListSupplier.getSyncOrNull(userWalletId) } returns AccountList.empty(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = listOf(cryptoCurrency),
|
||||
)
|
||||
every {
|
||||
cryptoCurrencyBalanceFetcher.invoke(userWalletId = userWalletId, currency = cryptoCurrency)
|
||||
} just Runs
|
||||
|
||||
createHandler(scope = this, defaultQueryParams(), isFromOnNewIntent = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify { singleAccountListFetcher.invoke(SingleAccountListFetcher.Params(userWalletId)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multicurrency wallet AND NOT isFromOnNewIntent WHEN handle deeplink THEN do not refresh accounts`() =
|
||||
runTest {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val cryptoCurrency = mockCryptoCurrency()
|
||||
mockMultiCurrencyWallet(userWalletId)
|
||||
mockSelectWallet(userWalletId)
|
||||
coEvery { singleAccountListSupplier.getSyncOrNull(userWalletId) } returns AccountList.empty(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = listOf(cryptoCurrency),
|
||||
)
|
||||
|
||||
createHandler(scope = this, defaultQueryParams(), isFromOnNewIntent = false)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { singleAccountListFetcher.invoke(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN single currency wallet AND isFromOnNewIntent WHEN handle deeplink THEN do not refresh accounts`() =
|
||||
runTest {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val cryptoCurrency = mockCryptoCurrency()
|
||||
mockSingleCurrencyWallet(userWalletId)
|
||||
mockSelectWallet(userWalletId)
|
||||
coEvery { singleAccountListSupplier.getSyncOrNull(userWalletId) } returns AccountList.empty(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = listOf(cryptoCurrency),
|
||||
)
|
||||
every { walletDeepLinkActionTrigger.selectWallet(userWalletId) } just Runs
|
||||
coEvery {
|
||||
walletBalanceFetcher.invoke(WalletBalanceFetcher.Params(userWalletId = userWalletId))
|
||||
} returns mockk()
|
||||
|
||||
createHandler(scope = this, defaultQueryParams(), isFromOnNewIntent = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { singleAccountListFetcher.invoke(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN crypto not found WHEN handle deeplink THEN redirect to main`() = runTest {
|
||||
val userWalletId = UserWalletId("011")
|
||||
mockMultiCurrencyWallet(userWalletId)
|
||||
mockSelectWallet(userWalletId)
|
||||
coEvery { singleAccountListSupplier.getSyncOrNull(userWalletId) } returns null
|
||||
|
||||
createHandler(scope = this, defaultQueryParams(), isFromOnNewIntent = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { appRouter.popTo(route = AppRoute.Wallet, onComplete = any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN refresh failed AND token in cache WHEN handle deeplink THEN push new route`() = runTest {
|
||||
val userWalletId = UserWalletId("011")
|
||||
val cryptoCurrency = mockCryptoCurrency()
|
||||
mockMultiCurrencyWallet(userWalletId)
|
||||
mockSelectWallet(userWalletId)
|
||||
coEvery {
|
||||
singleAccountListFetcher.invoke(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Either.Left(IllegalStateException("service unavailable"))
|
||||
coEvery { singleAccountListSupplier.getSyncOrNull(userWalletId) } returns AccountList.empty(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = listOf(cryptoCurrency),
|
||||
)
|
||||
every {
|
||||
cryptoCurrencyBalanceFetcher.invoke(userWalletId = userWalletId, currency = cryptoCurrency)
|
||||
} just Runs
|
||||
val expectedRoute = AppRoute.CurrencyDetails(userWalletId = userWalletId, currency = cryptoCurrency)
|
||||
|
||||
createHandler(scope = this, defaultQueryParams(), isFromOnNewIntent = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
appRouter.push(route = expectedRoute, onComplete = any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultQueryParams() = mapOf(
|
||||
WALLET_ID_KEY to "011",
|
||||
NETWORK_ID_KEY to "123",
|
||||
TOKEN_ID_KEY to "321",
|
||||
DERIVATION_PATH_KEY to "777",
|
||||
)
|
||||
|
||||
private fun mockCryptoCurrency() = mockk<CryptoCurrency> {
|
||||
every { network } returns mockk {
|
||||
every { rawId } returns "123"
|
||||
every { derivationPath } returns Network.DerivationPath.Card(value = "777")
|
||||
}
|
||||
every { id } returns CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkIdWithDerivationPath(rawId = "321", derivationPath = "777"),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID("321"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun mockMultiCurrencyWallet(userWalletId: UserWalletId) {
|
||||
every { getUserWalletUseCase.invoke(userWalletId) } returns Either.Right(
|
||||
value = mockk {
|
||||
every { isMultiCurrency } returns true
|
||||
every { walletId } returns userWalletId
|
||||
every { isLocked } returns false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun mockSingleCurrencyWallet(userWalletId: UserWalletId) {
|
||||
every { getUserWalletUseCase.invoke(userWalletId) } returns Either.Right(
|
||||
value = mockk {
|
||||
every { isMultiCurrency } returns false
|
||||
every { walletId } returns userWalletId
|
||||
every { isLocked } returns false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun mockSelectWallet(userWalletId: UserWalletId) {
|
||||
coEvery { selectWalletUseCase.invoke(userWalletId) } returns Either.Right(
|
||||
value = mockk { every { walletId } returns userWalletId },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createHandler(
|
||||
scope: CoroutineScope,
|
||||
queryParams: Map<String, String>,
|
||||
|
|
@ -479,6 +628,7 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
walletBalanceFetcher = walletBalanceFetcher,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
singleAccountListFetcher = singleAccountListFetcher,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSync,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue