diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/DefaultAddressSyncComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/DefaultAddressSyncComponent.kt index ab4f310b25..2cca709ec2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/DefaultAddressSyncComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/DefaultAddressSyncComponent.kt @@ -82,7 +82,7 @@ internal class DefaultAddressSyncComponent( model.onIntent(AddressSyncIntent.Sync) }, ) - AddressSyncState.NoTokens -> LaunchedEffect(Unit) { + AddressSyncState.Exit -> LaunchedEffect(Unit) { router.replaceAll(AppRoute.Wallet) } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncContract.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncContract.kt index f283b138e2..734633db22 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncContract.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncContract.kt @@ -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, + val isButtonLoading: Boolean = false, + ) : AddressSyncState() { + val currenciesCount: Int = currencies.size + } + data object Exit : AddressSyncState() } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModel.kt index d140c874f7..56a5c838ae 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModel.kt @@ -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 { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/ui/AddressSyncButtonScreen.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/ui/AddressSyncButtonScreen.kt index 94e2d60121..4b1bf6350c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/ui/AddressSyncButtonScreen.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/addresssync/ui/AddressSyncButtonScreen.kt @@ -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 = {}, ) } diff --git a/features/onboarding-v2/impl/src/test/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModelTest.kt b/features/onboarding-v2/impl/src/test/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModelTest.kt index 6cd4a728ba..c188cfeb15 100644 --- a/features/onboarding-v2/impl/src/test/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModelTest.kt +++ b/features/onboarding-v2/impl/src/test/kotlin/com/tangem/features/onboarding/v2/addresssync/model/AddressSyncModelTest.kt @@ -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)), ) @@ -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,7 +199,7 @@ internal class AddressSyncModelTest { ) } Assertions.assertEquals( - AddressSyncState.Success(currenciesCount = currencies.size), + AddressSyncState.Success(currencies), model.state.value, ) } @@ -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(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(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, ) }