Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-24 14:18:13 +01:00
commit 99acc9718b
5 changed files with 96 additions and 13 deletions

View file

@ -82,7 +82,7 @@ internal class DefaultAddressSyncComponent(
model.onIntent(AddressSyncIntent.Sync)
},
)
AddressSyncState.NoTokens -> LaunchedEffect(Unit) {
AddressSyncState.Exit -> LaunchedEffect(Unit) {
router.replaceAll(AppRoute.Wallet)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.onboarding.v2.addresssync.model
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
internal sealed interface AddressSyncIntent {
@ -9,6 +10,11 @@ internal sealed interface AddressSyncIntent {
internal sealed class AddressSyncState {
data object Loading : AddressSyncState()
data class Success(val currenciesCount: Int) : AddressSyncState()
data object NoTokens : AddressSyncState()
data class Success(
val currencies: List<CryptoCurrency>,
val isButtonLoading: Boolean = false,
) : AddressSyncState() {
val currenciesCount: Int = currencies.size
}
data object Exit : AddressSyncState()
}

View file

@ -12,11 +12,13 @@ import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.ShouldAskPermissionUseCase
import com.tangem.domain.settings.ShouldShowAskBiometryUseCase
import com.tangem.domain.tokens.MultiWalletAccountListFetcher
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -31,6 +33,7 @@ internal class AddressSyncModel @Inject constructor(
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
private val multiWalletAccountListFetcher: MultiWalletAccountListFetcher,
private val multiAccountListSupplier: MultiAccountListSupplier,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
paramsContainer: ParamsContainer,
) : Model() {
@ -107,7 +110,7 @@ internal class AddressSyncModel @Inject constructor(
params = MultiWalletAccountListFetcher.Params(userWalletId = walletId),
).fold(
ifLeft = {
state.value = AddressSyncState.NoTokens
state.value = AddressSyncState.Exit
},
ifRight = {
handleAddressSyncStep()
@ -125,9 +128,9 @@ internal class AddressSyncModel @Inject constructor(
}
.onEach { currencies ->
val updatedState = if (currencies.isEmpty()) {
AddressSyncState.NoTokens
AddressSyncState.Exit
} else {
AddressSyncState.Success(currenciesCount = currencies.size)
AddressSyncState.Success(currencies = currencies)
}
state.value = updatedState
}
@ -135,7 +138,23 @@ internal class AddressSyncModel @Inject constructor(
}
private fun startSyncing() {
TODO("Will be implemented during [REDACTED_TASK_KEY]")
modelScope.launch {
val successWithLoading = (state.value as AddressSyncState.Success).copy(isButtonLoading = true)
state.value = successWithLoading
val cryptoCurrencies = successWithLoading.currencies
derivePublicKeysUseCase(
userWalletId = walletId,
currencies = cryptoCurrencies,
).fold(
ifLeft = { throwable ->
state.value = successWithLoading.copy(
isButtonLoading = false,
)
TangemLogger.e("Failed to derive public keys", throwable)
},
ifRight = { state.value = AddressSyncState.Exit },
)
}
}
private companion object {

View file

@ -67,6 +67,7 @@ internal fun AddressSyncButtonScreen(
top = TangemTheme.dimens.spacing154,
bottom = TangemTheme.dimens.spacing16,
),
showProgress = state.isButtonLoading,
)
}
}
@ -98,7 +99,9 @@ private fun ColumnScope.AddressSyncDescription(currenciesCount: Int) {
private fun AddressSyncButtonScreenPreview() {
TangemThemePreview {
AddressSyncButtonScreen(
state = AddressSyncState.Success(currenciesCount = 2),
state = AddressSyncState.Success(
currencies = emptyList(),
),
onSyncClick = {},
)
}

View file

@ -12,6 +12,7 @@ import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.ShouldAskPermissionUseCase
import com.tangem.domain.settings.ShouldShowAskBiometryUseCase
import com.tangem.domain.tokens.MultiWalletAccountListFetcher
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.features.onboarding.v2.TitleProvider
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
@ -39,6 +40,7 @@ internal class AddressSyncModelTest {
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase = mockk()
private val multiWalletAccountListFetcher: MultiWalletAccountListFetcher = mockk()
private val multiAccountListSupplier: MultiAccountListSupplier = mockk()
private val derivePublicKeysUseCase: DerivePublicKeysUseCase = mockk()
private val paramsContainer: ParamsContainer = mockk()
private val testInnerNavigation = MutableStateFlow(
value = MultiWalletInnerNavigationState(
@ -62,6 +64,7 @@ internal class AddressSyncModelTest {
coEvery { shouldShowAskBiometryUseCase() } returns false
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
coEvery { multiWalletAccountListFetcher.invoke(any()) } returns Either.Right(Unit)
coEvery { derivePublicKeysUseCase(any(), any()) } returns Either.Right(Unit)
every { multiAccountListSupplier() } returns flowOf(
listOf(AccountList.empty(userWalletId = walletId)),
)
@ -154,7 +157,7 @@ internal class AddressSyncModelTest {
}
@Test
fun `GIVEN multiAccountListSupplier emits no currencies WHEN model is created THEN state is NoTokens`() = runTest {
fun `GIVEN multiAccountListSupplier emits no currencies WHEN model is created THEN state is Exit`() = runTest {
every { multiAccountListSupplier() } returns flowOf(
listOf(
AccountList.empty(
@ -172,7 +175,7 @@ internal class AddressSyncModelTest {
params = MultiWalletAccountListFetcher.Params(userWalletId = walletId)
)
}
Assertions.assertEquals(AddressSyncState.NoTokens, model.state.value)
Assertions.assertEquals(AddressSyncState.Exit, model.state.value)
}
@Test
@ -196,13 +199,13 @@ internal class AddressSyncModelTest {
)
}
Assertions.assertEquals(
AddressSyncState.Success(currenciesCount = currencies.size),
AddressSyncState.Success(currencies),
model.state.value,
)
}
@Test
fun `WHEN multiWalletAccountListFetcher emits error WHEN model is created THEN get NoToken state`() = runTest {
fun `WHEN multiWalletAccountListFetcher emits error WHEN model is created THEN get Exit state`() = runTest {
val currencies = listOf<CryptoCurrency>(mockk(), mockk(), mockk())
coEvery { multiWalletAccountListFetcher.invoke(any()) } returns Either.Left(
value = IllegalStateException("Test")
@ -226,7 +229,58 @@ internal class AddressSyncModelTest {
)
}
Assertions.assertEquals(
AddressSyncState.NoTokens,
AddressSyncState.Exit,
model.state.value,
)
}
@Test
fun `GIVEN success state WHEN Sync THEN state becomes Exit`() = runTest {
val currencies = listOf<CryptoCurrency>(mockk(), mockk(), mockk())
every { multiAccountListSupplier() } returns flowOf(
listOf(
AccountList.empty(
userWalletId = walletId,
cryptoCurrencies = currencies,
),
),
)
coEvery { derivePublicKeysUseCase(walletId, currencies) } returns Either.Right(Unit)
val model = createModel(this)
advanceUntilIdle()
model.onIntent(AddressSyncIntent.Sync)
advanceUntilIdle()
coVerify { derivePublicKeysUseCase(walletId, currencies) }
Assertions.assertEquals(AddressSyncState.Exit, model.state.value)
}
@Test
fun `GIVEN success state WHEN Sync AND derive fails THEN button loading is reset`() = runTest {
val currencies = listOf<CryptoCurrency>(mockk(), mockk(), mockk())
every { multiAccountListSupplier() } returns flowOf(
listOf(
AccountList.empty(
userWalletId = walletId,
cryptoCurrencies = currencies,
),
),
)
coEvery { derivePublicKeysUseCase(walletId, currencies) } returns Either.Left(
value = IllegalStateException("Test"),
)
val model = createModel(this)
advanceUntilIdle()
model.onIntent(AddressSyncIntent.Sync)
advanceUntilIdle()
coVerify { derivePublicKeysUseCase(walletId, currencies) }
Assertions.assertEquals(
AddressSyncState.Success(currencies = currencies, isButtonLoading = false),
model.state.value,
)
}
@ -254,6 +308,7 @@ internal class AddressSyncModelTest {
shouldAskPermissionUseCase = shouldAskPermissionUseCase,
multiWalletAccountListFetcher = multiWalletAccountListFetcher,
multiAccountListSupplier = multiAccountListSupplier,
derivePublicKeysUseCase = derivePublicKeysUseCase,
paramsContainer = paramsContainer,
)
}