Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-17 14:47:18 +05:00
parent fd4b5a3011
commit 8e824ed490
10 changed files with 171 additions and 105 deletions

View file

@ -231,8 +231,8 @@ internal object WalletsDomainModule {
@Singleton
fun providesGetSavedWalletChangesIdUseCase(
userWalletsListManager: UserWalletsListManager,
): GetSavedWalletChangesUseCase {
return GetSavedWalletChangesUseCase(
): GetSavedWalletsCountUseCase {
return GetSavedWalletsCountUseCase(
userWalletsListManager = userWalletsListManager,
)
}

View file

@ -37,6 +37,11 @@ internal class BiometricUserWalletsListManager(
.mapLatest { it.userWallets }
.distinctUntilChanged()
override val savedWalletsCount: Flow<Int>
get() = state
.mapLatest { walletsCount }
.distinctUntilChanged()
override val userWalletsSync: List<UserWallet>
get() = state.value.userWallets

View file

@ -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<Int>
get() = implementation
.transformLatest { impl ->
if (impl != null) {
emitAll(impl.savedWalletsCount)
}
}
override val userWalletsSync: List<UserWallet>
get() = requireImplementation.userWalletsSync

View file

@ -20,6 +20,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
.mapLatest { listOfNotNull(it.userWallet) }
.distinctUntilChanged()
override val savedWalletsCount: Flow<Int>
get() = state
.mapLatest { walletsCount }
.distinctUntilChanged()
override val selectedUserWallet: Flow<UserWallet>
get() = state
.mapLatest { it.userWallet }

View file

@ -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,

View file

@ -15,6 +15,9 @@ interface UserWalletsListManager {
/** [Flow] with all saved [UserWallet]s updates */
val userWallets: Flow<List<UserWallet>>
/** Count saved wallets updates */
val savedWalletsCount: Flow<Int>
/** [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<UserWallet>

View file

@ -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<List<UserWallet>> {
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()
}

View file

@ -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<UserWalletsListManager.Lockable>()
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<UserWalletsListManager.Lockable>()
val wallets = listOf(mockk<UserWallet>(), mockk<UserWallet>())
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)
}
}

View file

@ -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<UserWalletsListManager.Lockable>()
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<UserWalletsListManager.Lockable>()
val wallets = listOf(mockk<UserWallet>(), mockk<UserWallet>())
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<UserWalletsListManager.Lockable>()
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<UserWalletsListManager.Lockable>()
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()
}
}

View file

@ -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)