Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-03 12:25:53 +03:00
commit 7c629b68d1
1179 changed files with 24718 additions and 16703 deletions

View file

@ -9,7 +9,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
class UserWalletBuilder @AssistedInject constructor(
class ColdUserWalletBuilder @AssistedInject constructor(
@Assisted private val scanResponse: ScanResponse,
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
) {
@ -36,12 +36,12 @@ class UserWalletBuilder @AssistedInject constructor(
this.hasBackupError = hasBackupError
}
fun build(): UserWallet? {
fun build(): UserWallet.Cold? {
return with(scanResponse) {
UserWalletIdBuilder.scanResponse(scanResponse)
.build()
?.let {
UserWallet(
UserWallet.Cold(
walletId = it,
name = generateWalletNameUseCase(
productType = productType,
@ -60,6 +60,6 @@ class UserWalletBuilder @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(scanResponse: ScanResponse): UserWalletBuilder
fun create(scanResponse: ScanResponse): ColdUserWalletBuilder
}
}

View file

@ -0,0 +1,57 @@
package com.tangem.domain.wallets.delegate
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensure
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UpdateWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
import com.tangem.domain.wallets.models.copy
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
class DefaultUserWalletsSyncDelegate(
private val userWalletsListManager: UserWalletsListManager,
private val dispatchers: CoroutineDispatcherProvider,
) : UserWalletsSyncDelegate {
override suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> {
return renameUserWallet(userWalletId, name)
}
override suspend fun syncWallets(list: List<UserWalletRemoteInfo>): Either<UpdateWalletError, Unit> = either {
list.forEach { userWallet ->
renameUserWallet(userWallet.walletId, userWallet.name).bind()
}
}
// TODO remove dispatchers whnen UserWalletsListManager will be main safe
private suspend fun renameUserWallet(
userWalletId: UserWalletId,
name: String,
): Either<UpdateWalletError, UserWallet> = withContext(dispatchers.io) {
either {
val existingNames = userWalletsListManager.userWalletsSync
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
UpdateWalletError.NameAlreadyExists
}
val previousName = existingNames.firstOrNull { it.walletId == userWalletId }?.name.orEmpty()
if (previousName == name) {
raise(UpdateWalletError.NameAlreadyExists)
}
return@withContext when (
val result =
userWalletsListManager.update(userWalletId) { it.copy(name = name) }
) {
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
is CompletionResult.Success -> result.data
}
}
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.domain.wallets.delegate
import arrow.core.Either
import com.tangem.domain.wallets.models.UpdateWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
interface UserWalletsSyncDelegate {
suspend fun syncWallet(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet>
suspend fun syncWallets(list: List<UserWalletRemoteInfo>): Either<UpdateWalletError, Unit>
}

View file

@ -1,9 +1,12 @@
package com.tangem.domain.wallets.repository
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
import kotlinx.coroutines.flow.Flow
@Suppress("TooManyFunctions")
interface WalletsRepository {
suspend fun shouldSaveUserWalletsSync(): Boolean
@ -38,9 +41,21 @@ interface WalletsRepository {
suspend fun disableNFT(userWalletId: UserWalletId)
@Throws
suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean
fun notificationsEnabledStatus(userWalletId: UserWalletId): Flow<Boolean>
suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
@Throws
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
suspend fun setWalletName(walletId: String, walletName: String)
@Throws
suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo
@Throws
suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean = true): List<UserWalletRemoteInfo>
@Throws
suspend fun associateWallets(applicationId: String, wallets: List<UserWallet>)
}

View file

@ -0,0 +1,16 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
class AssociateWalletsWithApplicationIdUseCase(
private val walletsRepository: WalletsRepository,
) {
suspend operator fun invoke(applicationId: ApplicationId, wallets: List<UserWallet>): Either<Throwable, Unit> =
Either.catch {
walletsRepository.associateWallets(applicationId.value, wallets)
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.domain.wallets.usecase
import arrow.core.raise.catch
import com.tangem.blockchain.common.address.AddressType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId

View file

@ -8,10 +8,9 @@ class GetIsNotificationsEnabledUseCase(
private val walletsRepository: WalletsRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId, force: Boolean): Either<Throwable, Boolean> = Either.catch {
suspend operator fun invoke(userWalletId: UserWalletId): Either<Throwable, Boolean> = Either.catch {
walletsRepository.isNotificationsEnabled(
userWalletId = userWalletId,
force = force,
)
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.domain.wallets.usecase
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
class GetSavedWalletChangesUseCase(
private val userWalletsListManager: UserWalletsListManager,
) {
operator fun invoke(): Flow<List<UserWallet>> {
return userWalletsListManager.userWallets
.filter {
userWalletsListManager.asLockable() ?: return@filter false
return@filter if (userWalletsListManager.isLockedSync.not()) {
it.isNotEmpty()
} else {
false
}
}
.distinctUntilChanged()
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import kotlinx.coroutines.flow.Flow
class GetWalletNotificationsEnabledUseCase(
private val walletsRepository: WalletsRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> = walletsRepository
.notificationsEnabledStatus(userWalletId)
}

View file

@ -2,6 +2,7 @@ package com.tangem.domain.wallets.usecase
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -20,7 +21,7 @@ class IsNeedToBackupUseCase(private val userWalletsListManager: UserWalletsListM
if (wallet == null) {
false
} else {
wallet.scanResponse.card.backupStatus is CardDTO.BackupStatus.NoBackup
wallet is UserWallet.Cold && wallet.scanResponse.card.backupStatus is CardDTO.BackupStatus.NoBackup
}
}
}

View file

@ -2,38 +2,23 @@ package com.tangem.domain.wallets.usecase
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensure
import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
import com.tangem.domain.wallets.models.UpdateWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import com.tangem.domain.wallets.repository.WalletsRepository
/**
* Use case for rename user wallet
*
* @property userWalletsListManager user wallets list manager
*/
class RenameWalletUseCase(
private val userWalletsListManager: UserWalletsListManager,
private val dispatchers: CoroutineDispatcherProvider,
private val walletsRepository: WalletsRepository,
private val userWalletsSyncDelegate: UserWalletsSyncDelegate,
) {
suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> =
withContext(dispatchers.io) {
either {
val existingNames = userWalletsListManager.userWalletsSync
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
UpdateWalletError.NameAlreadyExists
}
when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) {
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
is CompletionResult.Success -> result.data
}
either {
runCatching {
walletsRepository.setWalletName(userWalletId.stringValue, name)
}
userWalletsSyncDelegate.syncWallet(userWalletId, name).bind()
}
}

View file

@ -1,11 +1,13 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
class SetNotificationsEnabledUseCase(
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId, isEnabled: Boolean): Either<Throwable, Unit> =
@ -14,5 +16,11 @@ class SetNotificationsEnabledUseCase(
userWalletId = userWalletId,
isEnabled = isEnabled,
)
currenciesRepository.syncTokens(userWalletId)
}.onLeft {
walletsRepository.setNotificationsEnabled(
userWalletId = userWalletId,
isEnabled = !isEnabled,
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.wallets.usecase
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
import com.tangem.domain.wallets.repository.WalletsRepository
class UpdateRemoteWalletsInfoUseCase(
private val walletsRepository: WalletsRepository,
private val userWalletsSyncDelegate: UserWalletsSyncDelegate,
) {
suspend operator fun invoke(applicationId: ApplicationId): Either<Throwable, Unit> = Either.catch {
val walletsInfo = walletsRepository.getWalletsInfo(applicationId.value)
userWalletsSyncDelegate.syncWallets(walletsInfo)
}
}

View file

@ -0,0 +1,76 @@
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)
}
}