From 8e824ed49013df1be074bca1bbaa36cf6c41c815 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Jul 2025 14:47:18 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/WalletsDomainModule.kt | 4 +- .../BiometricUserWalletsListManager.kt | 5 + .../GeneralUserWalletsListManager.kt | 8 + .../RuntimeUserWalletsListManager.kt | 5 + .../tangem/tap/features/main/MainViewModel.kt | 4 +- .../wallets/legacy/UserWalletsListManager.kt | 3 + ...Case.kt => GetSavedWalletsCountUseCase.kt} | 20 ++- .../GetSavedWalletChangesUseCaseTest.kt | 76 ---------- .../GetSavedWalletsCountUseCaseTest.kt | 137 ++++++++++++++++++ .../model/WalletSettingsModel.kt | 14 -- 10 files changed, 171 insertions(+), 105 deletions(-) rename domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/{GetSavedWalletChangesUseCase.kt => GetSavedWalletsCountUseCase.kt} (55%) delete mode 100644 domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt create mode 100644 domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 684a7f0cf7..f2f9912270 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -231,8 +231,8 @@ internal object WalletsDomainModule { @Singleton fun providesGetSavedWalletChangesIdUseCase( userWalletsListManager: UserWalletsListManager, - ): GetSavedWalletChangesUseCase { - return GetSavedWalletChangesUseCase( + ): GetSavedWalletsCountUseCase { + return GetSavedWalletsCountUseCase( userWalletsListManager = userWalletsListManager, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 2d52d344fe..4774cebee1 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -37,6 +37,11 @@ internal class BiometricUserWalletsListManager( .mapLatest { it.userWallets } .distinctUntilChanged() + override val savedWalletsCount: Flow + get() = state + .mapLatest { walletsCount } + .distinctUntilChanged() + override val userWalletsSync: List get() = state.value.userWallets diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 4cad89ba5d..1be31c95b6 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -60,6 +60,14 @@ internal class GeneralUserWalletsListManager( // As a result subscription occurs on empty flow, than will not change if user wallets are available .filter { requireImplementation.hasUserWallets } + override val savedWalletsCount: Flow + get() = implementation + .transformLatest { impl -> + if (impl != null) { + emitAll(impl.savedWalletsCount) + } + } + override val userWalletsSync: List get() = requireImplementation.userWalletsSync diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 4d211d9b88..43bd7646e0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -20,6 +20,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { .mapLatest { listOfNotNull(it.userWallet) } .distinctUntilChanged() + override val savedWalletsCount: Flow + get() = state + .mapLatest { walletsCount } + .distinctUntilChanged() + override val selectedUserWallet: Flow get() = state .mapLatest { it.userWallet } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 404a978379..d3a65f5ace 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -39,7 +39,7 @@ import com.tangem.domain.staking.FetchStakingTokensUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase -import com.tangem.domain.wallets.usecase.GetSavedWalletChangesUseCase +import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase import com.tangem.feature.swap.analytics.StoriesEvents import com.tangem.features.onramp.deeplink.OnrampDeepLink @@ -82,7 +82,7 @@ internal class MainViewModel @Inject constructor( private val onrampDeepLinkFactory: OnrampDeepLink.Factory, private val notificationsToggles: NotificationsFeatureToggles, private val getApplicationIdUseCase: GetApplicationIdUseCase, - private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase, + private val subscribeOnWalletsUseCase: GetSavedWalletsCountUseCase, private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase, private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase, private val sendPushTokenUseCase: SendPushTokenUseCase, diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index bd117a8c8e..c61566c02c 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -15,6 +15,9 @@ interface UserWalletsListManager { /** [Flow] with all saved [UserWallet]s updates */ val userWallets: Flow> + /** Count saved wallets updates */ + val savedWalletsCount: Flow + /** [Flow] with selected [UserWallet] updates */ @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") val selectedUserWallet: Flow diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt similarity index 55% rename from domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt rename to domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt index afcfe40a89..8bc40b9305 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCase.kt @@ -4,23 +4,21 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.asLockable import com.tangem.domain.wallets.legacy.isLockedSync import com.tangem.domain.wallets.models.UserWallet -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.* -class GetSavedWalletChangesUseCase( +class GetSavedWalletsCountUseCase( private val userWalletsListManager: UserWalletsListManager, ) { operator fun invoke(): Flow> { - return userWalletsListManager.userWallets - .filter { + return userWalletsListManager.savedWalletsCount + .filter { count -> + if (count == 0) return@filter true userWalletsListManager.asLockable() ?: return@filter false - return@filter if (userWalletsListManager.isLockedSync.not()) { - it.isNotEmpty() - } else { - false - } + return@filter userWalletsListManager.isLockedSync.not() + } + .map { + userWalletsListManager.userWalletsSync } .distinctUntilChanged() } diff --git a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt deleted file mode 100644 index 25e0cb17db..0000000000 --- a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletChangesUseCaseTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.domain.wallets.usecase - -import com.google.common.truth.Truth.assertThat -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.asLockable -import com.tangem.domain.wallets.legacy.isLockedSync -import com.tangem.domain.wallets.models.UserWallet -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test - -class GetSavedWalletChangesUseCaseTest { - - private lateinit var useCase: GetSavedWalletChangesUseCase - private lateinit var userWalletsListManager: UserWalletsListManager - - @Before - fun setup() { - userWalletsListManager = mockk() - useCase = GetSavedWalletChangesUseCase(userWalletsListManager) - mockkStatic("com.tangem.domain.wallets.legacy.UserWalletsListManagerExtensionsKt") - } - - @Test - fun `GIVEN manager is not lockable WHEN invoke THEN return empty list`() = runTest { - // GIVEN - every { userWalletsListManager.isLockable } returns false - every { userWalletsListManager.userWallets } returns flowOf(emptyList()) - every { userWalletsListManager.isLockedSync } returns false - every { userWalletsListManager.asLockable() } returns null - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isNull() - } - - @Test - fun `GIVEN manager is locked WHEN invoke THEN return empty list`() = runTest { - // GIVEN - val mockLockable = mockk() - every { userWalletsListManager.isLockable } returns true - every { userWalletsListManager.userWallets } returns flowOf(emptyList()) - every { userWalletsListManager.isLockedSync } returns true - every { userWalletsListManager.asLockable() } returns mockLockable - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isNull() - } - - @Test - fun `GIVEN manager is not locked and has wallets WHEN invoke THEN return wallets list`() = runTest { - // GIVEN - val mockLockable = mockk() - val wallets = listOf(mockk(), mockk()) - every { userWalletsListManager.isLockable } returns true - every { userWalletsListManager.userWallets } returns flowOf(wallets) - every { userWalletsListManager.isLockedSync } returns false - every { userWalletsListManager.asLockable() } returns mockLockable - - // WHEN - val result = useCase().firstOrNull() - - // THEN - assertThat(result).isEqualTo(wallets) - } -} \ No newline at end of file diff --git a/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt new file mode 100644 index 0000000000..7949741300 --- /dev/null +++ b/domain/wallets/src/test/java/com/tangem/domain/wallets/usecase/GetSavedWalletsCountUseCaseTest.kt @@ -0,0 +1,137 @@ +package com.tangem.domain.wallets.usecase + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.legacy.asLockable +import com.tangem.domain.wallets.legacy.isLockedSync +import com.tangem.domain.wallets.models.UserWallet +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +class GetSavedWalletsCountUseCaseTest { + + private lateinit var useCase: GetSavedWalletsCountUseCase + private lateinit var userWalletsListManager: UserWalletsListManager + + @Before + fun setup() { + userWalletsListManager = mockk() + useCase = GetSavedWalletsCountUseCase(userWalletsListManager) + mockkStatic("com.tangem.domain.wallets.legacy.UserWalletsListManagerExtensionsKt") + } + + @Test + fun `GIVEN manager is not lockable WHEN invoke THEN return empty list`() = runTest { + // GIVEN + every { userWalletsListManager.isLockable } returns false + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns null + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is locked WHEN invoke THEN return empty list`() = runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns true + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is not locked and has wallets WHEN invoke THEN return wallets list`() = runTest { + // GIVEN + val mockLockable = mockk() + val wallets = listOf(mockk(), mockk()) + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(wallets.size) + every { userWalletsListManager.userWallets } returns flowOf(wallets) + every { userWalletsListManager.userWalletsSync } returns wallets + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEqualTo(wallets) + } + + @Test + fun `GIVEN manager is not lockable and savedWalletsCount is zero WHEN invoke THEN return empty list`() = runTest { + // GIVEN + every { userWalletsListManager.isLockable } returns false + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns null + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is lockable and locked and savedWalletsCount is zero WHEN invoke THEN return empty list`() = + runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns true + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } + + @Test + fun `GIVEN manager is lockable and unlocked and savedWalletsCount is zero WHEN invoke THEN return empty list`() = + runTest { + // GIVEN + val mockLockable = mockk() + every { userWalletsListManager.isLockable } returns true + every { userWalletsListManager.savedWalletsCount } returns flowOf(0) + every { userWalletsListManager.userWallets } returns flowOf(emptyList()) + every { userWalletsListManager.userWalletsSync } returns emptyList() + every { userWalletsListManager.isLockedSync } returns false + every { userWalletsListManager.asLockable() } returns mockLockable + + // WHEN + val result = useCase().firstOrNull() + + // THEN + assertThat(result).isEmpty() + } +} \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 8861cf7891..b85bb4de06 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -24,7 +24,6 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase -import com.tangem.domain.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.wallets.models.UserWallet @@ -45,7 +44,6 @@ import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnaly import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -74,8 +72,6 @@ internal class WalletSettingsModel @Inject constructor( private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase, private val settingsManager: SettingsManager, private val permissionsRepository: PermissionRepository, - private val getApplicationIdUseCase: GetApplicationIdUseCase, - private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase, ) : Model() { val params: WalletSettingsComponent.Params = paramsContainer.require() @@ -198,20 +194,10 @@ internal class WalletSettingsModel @Inject constructor( if (hasUserWallets) { router.pop() } else { - clearWalletsAssociatedWithApplicationId() router.replaceAll(AppRoute.Home) } } - private fun clearWalletsAssociatedWithApplicationId() = modelScope.launch(NonCancellable) { - getApplicationIdUseCase().onRight { applicationId -> - associateWalletsWithApplicationIdUseCase(applicationId, emptyList()) - .onLeft { - Timber.e("Unable to associate empty wallets with application ID: $it") - } - } - } - private fun onLinkMoreCardsClick(scanResponse: ScanResponse) { analyticsEventHandler.send(Settings.ButtonCreateBackup) analyticsContextProxy.addContext(scanResponse)