Updated on 2026-08-14
This commit is contained in:
parent
e381fafeb8
commit
7cb4b57fe9
21 changed files with 556 additions and 230 deletions
|
|
@ -3,10 +3,13 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
|
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
|
||||||
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
||||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||||
import com.tangem.domain.account.usecase.*
|
import com.tangem.domain.account.usecase.*
|
||||||
|
import com.tangem.feature.referral.data.ExternalReferralRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -43,9 +46,15 @@ internal object AccountDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideArchiveCryptoPortfolioUseCase(
|
fun provideArchiveCryptoPortfolioUseCase(
|
||||||
|
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
|
referralRepository: ExternalReferralRepository,
|
||||||
): ArchiveCryptoPortfolioUseCase {
|
): ArchiveCryptoPortfolioUseCase {
|
||||||
return ArchiveCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
return ArchiveCryptoPortfolioUseCase(
|
||||||
|
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||||
|
crudRepository = accountsCRUDRepository,
|
||||||
|
referralRepository = referralRepository,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.domain.account.models
|
package com.tangem.domain.account.models
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
import com.tangem.domain.models.TokensGroupType
|
import com.tangem.domain.models.TokensGroupType
|
||||||
import com.tangem.domain.models.TokensSortType
|
import com.tangem.domain.models.TokensSortType
|
||||||
import com.tangem.domain.models.TotalFiatBalance
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
|
|
@ -40,4 +41,14 @@ data class AccountStatusList(
|
||||||
fun flattenCurrencies(): List<CryptoCurrencyStatus> = accountStatuses
|
fun flattenCurrencies(): List<CryptoCurrencyStatus> = accountStatuses
|
||||||
.map { accountStatus -> accountStatus.flattenCurrencies() }
|
.map { accountStatus -> accountStatus.flattenCurrencies() }
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
|
fun toAccountList(): Either<AccountList.Error, AccountList> {
|
||||||
|
return AccountList(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
accounts = accountStatuses.map(AccountStatus::account),
|
||||||
|
totalAccounts = totalAccounts,
|
||||||
|
sortType = sortType,
|
||||||
|
groupType = groupType,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
package com.tangem.domain.account.usecase
|
|
||||||
|
|
||||||
import arrow.core.None
|
|
||||||
import arrow.core.left
|
|
||||||
import arrow.core.right
|
|
||||||
import arrow.core.toOption
|
|
||||||
import com.google.common.truth.Truth
|
|
||||||
import com.tangem.domain.account.models.AccountList
|
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
|
||||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase.Error
|
|
||||||
import com.tangem.domain.models.account.AccountId
|
|
||||||
import com.tangem.domain.models.account.DerivationIndex
|
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
|
||||||
import com.tangem.test.mock.MockAccounts.createAccount
|
|
||||||
import io.mockk.*
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class ArchiveCryptoPortfolioUseCaseTest {
|
|
||||||
|
|
||||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
|
||||||
private val useCase = ArchiveCryptoPortfolioUseCase(crudRepository)
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun resetMocks() {
|
|
||||||
clearMocks(crudRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val account = createAccount(derivationIndex = 1)
|
|
||||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
|
||||||
val accountId = account.accountId
|
|
||||||
|
|
||||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
|
||||||
|
|
||||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = useCase(accountId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = Unit.right()
|
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
|
||||||
|
|
||||||
coVerifyOrder {
|
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
|
||||||
crudRepository.saveAccounts(updatedAccountList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invoke should return error if getAccounts returns None`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val accountId = AccountId.forCryptoPortfolio(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
derivationIndex = DerivationIndex.Main,
|
|
||||||
)
|
|
||||||
|
|
||||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = useCase(accountId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
|
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
|
||||||
|
|
||||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
|
||||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invoke should return error if getAccounts throws exception`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val accountId = AccountId.forCryptoPortfolio(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
derivationIndex = DerivationIndex.Main,
|
|
||||||
)
|
|
||||||
|
|
||||||
val exception = IllegalStateException("Test error")
|
|
||||||
|
|
||||||
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = useCase(accountId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = Error.DataOperationFailed(exception).left()
|
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
|
||||||
|
|
||||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
|
||||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invoke should return error if account not found`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val accountList = AccountList.empty(userWalletId)
|
|
||||||
val accountId = AccountId.forCryptoPortfolio(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
|
||||||
)
|
|
||||||
|
|
||||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = useCase(accountId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
|
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
|
||||||
|
|
||||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
|
||||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
|
||||||
// Arrange
|
|
||||||
val account = createAccount(derivationIndex = 1)
|
|
||||||
val accountList = (AccountList.empty(userWalletId) + account).getOrNull()!!
|
|
||||||
val accountId = account.accountId
|
|
||||||
|
|
||||||
val updatedAccountList = (accountList - account).getOrNull()!!
|
|
||||||
|
|
||||||
val exception = IllegalStateException("Save failed")
|
|
||||||
|
|
||||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
|
||||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
|
||||||
|
|
||||||
// Act
|
|
||||||
val actual = useCase(accountId)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
val expected = Error.DataOperationFailed(exception).left()
|
|
||||||
Truth.assertThat(actual).isEqualTo(expected)
|
|
||||||
|
|
||||||
coVerifyOrder {
|
|
||||||
crudRepository.getAccountListSync(userWalletId)
|
|
||||||
crudRepository.saveAccounts(updatedAccountList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
val userWalletId = UserWalletId("011")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
||||||
api(projects.domain.quotes)
|
api(projects.domain.quotes)
|
||||||
api(projects.domain.models)
|
api(projects.domain.models)
|
||||||
api(projects.domain.networks)
|
api(projects.domain.networks)
|
||||||
|
api(projects.domain.referral)
|
||||||
api(projects.domain.staking)
|
api(projects.domain.staking)
|
||||||
api(projects.domain.tokens)
|
api(projects.domain.tokens)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
|
|
@ -42,7 +43,7 @@ dependencies {
|
||||||
// end
|
// end
|
||||||
|
|
||||||
testRuntimeOnly(deps.test.junit5.engine)
|
testRuntimeOnly(deps.test.junit5.engine)
|
||||||
testImplementation(tangemDeps.blockchain)
|
|
||||||
testImplementation(projects.common.test)
|
testImplementation(projects.common.test)
|
||||||
testImplementation(projects.test.core)
|
testImplementation(projects.test.core)
|
||||||
|
testImplementation(projects.test.mock)
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ internal object AccountStatusUseCaseModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideManageCryptoCurrenciesUseCase(
|
fun provideManageCryptoCurrenciesUseCase(
|
||||||
singleAccountListSupplier: SingleAccountListSupplier,
|
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
|
|
@ -103,7 +103,7 @@ internal object AccountStatusUseCaseModule {
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): ManageCryptoCurrenciesUseCase {
|
): ManageCryptoCurrenciesUseCase {
|
||||||
return ManageCryptoCurrenciesUseCase(
|
return ManageCryptoCurrenciesUseCase(
|
||||||
singleAccountListSupplier = singleAccountListSupplier,
|
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||||
accountsCRUDRepository = accountsCRUDRepository,
|
accountsCRUDRepository = accountsCRUDRepository,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,48 @@
|
||||||
package com.tangem.domain.account.usecase
|
package com.tangem.domain.account.status.usecase
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import arrow.core.raise.Raise
|
import arrow.core.raise.*
|
||||||
import arrow.core.raise.catch
|
|
||||||
import arrow.core.raise.either
|
|
||||||
import com.tangem.domain.account.models.AccountList
|
import com.tangem.domain.account.models.AccountList
|
||||||
|
import com.tangem.domain.account.models.AccountStatusList
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||||
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
import com.tangem.domain.models.account.AccountId
|
import com.tangem.domain.models.account.AccountId
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.referral.ReferralRepository
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use case for archiving a crypto portfolio.
|
* Use case for archiving a crypto portfolio.
|
||||||
* This class provides functionality to archive a specific account within a user's crypto portfolio.
|
* This class provides functionality to archive a specific account within a user's crypto portfolio.
|
||||||
* It ensures that the account exists and meets the necessary requirements before performing the operation.
|
* It ensures that the account exists and meets the necessary requirements before performing the operation.
|
||||||
*
|
*
|
||||||
|
* @property singleAccountStatusListSupplier supplier to get the list of account statuses
|
||||||
* @property crudRepository repository for performing CRUD operations on accounts
|
* @property crudRepository repository for performing CRUD operations on accounts
|
||||||
|
* @property referralRepository repository for handling referral status checks
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
class ArchiveCryptoPortfolioUseCase(
|
class ArchiveCryptoPortfolioUseCase(
|
||||||
|
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
private val crudRepository: AccountsCRUDRepository,
|
private val crudRepository: AccountsCRUDRepository,
|
||||||
|
private val referralRepository: ReferralRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/** Archives the specified account by its [accountId] */
|
/** Archives the specified account by its [accountId] */
|
||||||
suspend operator fun invoke(accountId: AccountId): Either<Error, Unit> = either {
|
suspend operator fun invoke(accountId: AccountId): Either<Error, Unit> = either {
|
||||||
val accountList = getAccountList(userWalletId = accountId.userWalletId)
|
val accountStatusList = getAccountStatusList(userWalletId = accountId.userWalletId)
|
||||||
|
val accountList = accountStatusList.toAccountList().getOrElse {
|
||||||
|
raise(Error.CriticalTechError.AccountListRequirementsNotMet(cause = it))
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure(accountList.accounts.any { it.accountId == accountId }) {
|
||||||
|
Error.CriticalTechError.AccountNotFound(accountId = accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkReferralStatus(accountStatusList = accountStatusList, accountId = accountId)
|
||||||
|
|
||||||
val archivingAccount = accountList.accounts
|
val archivingAccount = accountList.accounts
|
||||||
.firstOrNull { it.accountId == accountId }
|
.firstOrNull { it.accountId == accountId }
|
||||||
|
|
@ -38,16 +55,48 @@ class ArchiveCryptoPortfolioUseCase(
|
||||||
saveAccounts(updatedAccounts)
|
saveAccounts(updatedAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
private suspend fun Raise<Error>.checkReferralStatus(accountStatusList: AccountStatusList, accountId: AccountId) {
|
||||||
return catch(
|
val referralStatus = catch(
|
||||||
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
block = { referralRepository.getReferralStatus(userWalletId = accountStatusList.userWalletId.stringValue) },
|
||||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||||
)
|
)
|
||||||
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }
|
|
||||||
|
val referralToken = referralStatus.token
|
||||||
|
val address = referralStatus.address
|
||||||
|
|
||||||
|
if (!referralStatus.isActive || referralToken == null || address == null) return
|
||||||
|
|
||||||
|
val account = accountStatusList.accountStatuses
|
||||||
|
.asSequence()
|
||||||
|
.filterIsInstance<AccountStatus.CryptoPortfolio>()
|
||||||
|
.firstOrNull { it.accountId == accountId }
|
||||||
|
|
||||||
|
ensureNotNull(account) {
|
||||||
|
Error.CriticalTechError.AccountNotFound(accountId = accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val statuses = account.flattenCurrencies()
|
||||||
|
|
||||||
|
val hasNotReferralToken = statuses.none { status ->
|
||||||
|
val currency = status.currency
|
||||||
|
|
||||||
|
currency.network.backendId == referralToken.networkId &&
|
||||||
|
(currency as? CryptoCurrency.Token)?.contractAddress == referralToken.contractAddress &&
|
||||||
|
status.value.networkAddress?.availableAddresses?.any { it.value == address } == true
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure(hasNotReferralToken) { Error.ActiveReferralStatus }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun Raise<Error>.getAccountStatusList(userWalletId: UserWalletId): AccountStatusList {
|
||||||
|
return singleAccountStatusListSupplier.getSyncOrNull(
|
||||||
|
SingleAccountStatusListProducer.Params(userWalletId = userWalletId),
|
||||||
|
)
|
||||||
|
?: raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
||||||
catch(
|
arrow.core.raise.catch(
|
||||||
block = { crudRepository.saveAccounts(accountList) },
|
block = { crudRepository.saveAccounts(accountList) },
|
||||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||||
)
|
)
|
||||||
|
|
@ -58,9 +107,14 @@ class ArchiveCryptoPortfolioUseCase(
|
||||||
*/
|
*/
|
||||||
sealed interface Error {
|
sealed interface Error {
|
||||||
|
|
||||||
|
val tag: String
|
||||||
|
get() = this.javaClass.simpleName
|
||||||
|
|
||||||
|
data object ActiveReferralStatus : Error
|
||||||
|
|
||||||
/** Error indicating that a data operation failed */
|
/** Error indicating that a data operation failed */
|
||||||
data class DataOperationFailed(val cause: Throwable) : Error {
|
data class DataOperationFailed(val cause: Throwable) : Error {
|
||||||
override fun toString(): String = "$this: Data operation failed: ${cause.message ?: "Unknown error"}"
|
override fun toString(): String = "$tag: Data operation failed: ${cause.message ?: "Unknown error"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,6 +82,7 @@ class GetAccountCurrencyByAddressUseCase(
|
||||||
for (id in userWalletIds) {
|
for (id in userWalletIds) {
|
||||||
val networkStatus = multiNetworkStatusSupplier.getSyncOrNull(
|
val networkStatus = multiNetworkStatusSupplier.getSyncOrNull(
|
||||||
params = MultiNetworkStatusProducer.Params(userWalletId = id),
|
params = MultiNetworkStatusProducer.Params(userWalletId = id),
|
||||||
|
timeMillis = 1000L,
|
||||||
)
|
)
|
||||||
?.firstOrNull { it.getAddress() == address }
|
?.firstOrNull { it.getAddress() == address }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@ package com.tangem.domain.account.status.usecase
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.raise.Raise
|
import arrow.core.raise.Raise
|
||||||
import arrow.core.raise.catch
|
import arrow.core.raise.catch
|
||||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||||
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
|
||||||
import com.tangem.domain.core.utils.eitherOn
|
import com.tangem.domain.core.utils.eitherOn
|
||||||
import com.tangem.domain.express.ExpressServiceFetcher
|
import com.tangem.domain.express.ExpressServiceFetcher
|
||||||
import com.tangem.domain.express.models.ExpressAsset
|
import com.tangem.domain.express.models.ExpressAsset
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.account.AccountId
|
import com.tangem.domain.models.account.AccountId
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.networks.utils.NetworksCleaner
|
import com.tangem.domain.networks.utils.NetworksCleaner
|
||||||
|
|
@ -27,7 +29,7 @@ import timber.log.Timber
|
||||||
/**
|
/**
|
||||||
* Use case for saving crypto currencies to a specific account.
|
* Use case for saving crypto currencies to a specific account.
|
||||||
*
|
*
|
||||||
* @property singleAccountListSupplier Supplier to get account details.
|
* @property singleAccountStatusListSupplier Supplier for fetching the status of a single account.
|
||||||
* @property accountsCRUDRepository Repository for performing CRUD operations on accounts.
|
* @property accountsCRUDRepository Repository for performing CRUD operations on accounts.
|
||||||
* @property currenciesRepository Repository for managing currencies.
|
* @property currenciesRepository Repository for managing currencies.
|
||||||
* @property derivationsRepository Repository for deriving public keys.
|
* @property derivationsRepository Repository for deriving public keys.
|
||||||
|
|
@ -43,7 +45,7 @@ import timber.log.Timber
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
class ManageCryptoCurrenciesUseCase(
|
class ManageCryptoCurrenciesUseCase(
|
||||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
private val derivationsRepository: DerivationsRepository,
|
private val derivationsRepository: DerivationsRepository,
|
||||||
|
|
@ -76,12 +78,13 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
|
|
||||||
val userWalletId = accountId.userWalletId
|
val userWalletId = accountId.userWalletId
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
val account = getAccount(accountId = accountId)
|
val accountStatus = getAccountStatus(accountId = accountId)
|
||||||
|
|
||||||
val modifiedCurrencyList = account.cryptoCurrencies.modify(add = add, remove = remove)
|
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||||
|
.modify(add = add, remove = remove)
|
||||||
|
|
||||||
saveAccount(
|
saveAccount(
|
||||||
account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
|
||||||
)
|
)
|
||||||
|
|
||||||
val isDerivingFailed = derivePublicKeys(
|
val isDerivingFailed = derivePublicKeys(
|
||||||
|
|
@ -117,10 +120,10 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
val userWalletId = accountId.userWalletId
|
val userWalletId = accountId.userWalletId
|
||||||
|
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
val account = getAccount(accountId = accountId)
|
val accountStatus = getAccountStatus(accountId = accountId)
|
||||||
|
|
||||||
val foundToken = account.cryptoCurrencies
|
val foundToken = accountStatus.tokenList.flattenCurrencies()
|
||||||
.filterIsInstance<CryptoCurrency.Token>()
|
.mapNotNull { it.currency as? CryptoCurrency.Token }
|
||||||
.firstOrNull {
|
.firstOrNull {
|
||||||
it.network.backendId == networkId &&
|
it.network.backendId == networkId &&
|
||||||
!it.isCustom &&
|
!it.isCustom &&
|
||||||
|
|
@ -131,9 +134,10 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
|
|
||||||
val tokenToAdd = findToken(userWalletId, contractAddress, networkId)
|
val tokenToAdd = findToken(userWalletId, contractAddress, networkId)
|
||||||
|
|
||||||
val modifiedCurrencyList = account.cryptoCurrencies.modify(add = listOf(tokenToAdd))
|
val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies()
|
||||||
|
.modify(add = listOf(tokenToAdd))
|
||||||
|
|
||||||
saveAccount(account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
saveAccount(account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()))
|
||||||
|
|
||||||
parallelUpdatingScope.launch {
|
parallelUpdatingScope.launch {
|
||||||
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd))
|
||||||
|
|
@ -144,26 +148,31 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Throwable>.getAccount(accountId: AccountId): Account.CryptoPortfolio {
|
private suspend fun Raise<Throwable>.getAccountStatus(accountId: AccountId): AccountStatus.CryptoPortfolio {
|
||||||
val accountList = singleAccountListSupplier.getSyncOrNull(
|
val accountStatusList = singleAccountStatusListSupplier.getSyncOrNull(
|
||||||
params = SingleAccountListProducer.Params(userWalletId = accountId.userWalletId),
|
params = SingleAccountStatusListProducer.Params(userWalletId = accountId.userWalletId),
|
||||||
) ?: raise(IllegalStateException("No accounts for wallet ${accountId.userWalletId}"))
|
) ?: raise(IllegalStateException("No accounts for wallet ${accountId.userWalletId}"))
|
||||||
|
|
||||||
return accountList.accounts.firstOrNull { it.accountId == accountId } as? Account.CryptoPortfolio
|
return accountStatusList.accountStatuses
|
||||||
|
.firstOrNull { it.accountId == accountId } as? AccountStatus.CryptoPortfolio
|
||||||
?: raise(IllegalStateException("No account with id $accountId"))
|
?: raise(IllegalStateException("No account with id $accountId"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Set<CryptoCurrency>.modify(
|
private fun List<CryptoCurrencyStatus>.modify(
|
||||||
add: List<CryptoCurrency>,
|
add: List<CryptoCurrency>,
|
||||||
remove: List<CryptoCurrency> = emptyList(),
|
remove: List<CryptoCurrency> = emptyList(),
|
||||||
): ModifiedCurrencyList {
|
): ModifiedCurrencyList {
|
||||||
val mutableCurrencies = this.toMutableList()
|
val mutableCurrencies = this.map(CryptoCurrencyStatus::currency).toMutableList()
|
||||||
val added = mutableListOf<CryptoCurrency>()
|
val added = mutableListOf<CryptoCurrency>()
|
||||||
val removed = mutableListOf<CryptoCurrency>()
|
val removed = mutableListOf<CryptoCurrency>()
|
||||||
|
|
||||||
val existingCurrenciesById = mutableCurrencies.associateBy(::TempID)
|
val existingCurrenciesById = this.associateBy(::TempID)
|
||||||
|
|
||||||
add.groupByNetwork { !existingCurrenciesById.containsKey(it) }
|
add.groupByNetwork { tempID ->
|
||||||
|
val found = existingCurrenciesById[tempID] ?: return@groupByNetwork true
|
||||||
|
|
||||||
|
found.value is CryptoCurrencyStatus.MissedDerivation
|
||||||
|
}
|
||||||
.forEach { (network, currenciesById) ->
|
.forEach { (network, currenciesById) ->
|
||||||
val coinTempId = TempID(network)
|
val coinTempId = TempID(network)
|
||||||
|
|
||||||
|
|
@ -204,20 +213,6 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
return ModifiedCurrencyList(added = added, removed = removed, total = mutableCurrencies)
|
return ModifiedCurrencyList(added = added, removed = removed, total = mutableCurrencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Throwable>.saveAccount(account: Account.CryptoPortfolio) {
|
|
||||||
catch(
|
|
||||||
block = { accountsCRUDRepository.saveAccount(account) },
|
|
||||||
catch = ::raise,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun derivePublicKeys(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
currencies: List<CryptoCurrency>,
|
|
||||||
): Either<Throwable, Unit> = Either.catch {
|
|
||||||
derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<CryptoCurrency>.groupByNetwork(
|
private fun List<CryptoCurrency>.groupByNetwork(
|
||||||
valuePredicate: (TempID) -> Boolean,
|
valuePredicate: (TempID) -> Boolean,
|
||||||
): LinkedHashMap<Network, MutableMap<TempID, CryptoCurrency>> {
|
): LinkedHashMap<Network, MutableMap<TempID, CryptoCurrency>> {
|
||||||
|
|
@ -230,13 +225,27 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
val id = TempID(currency)
|
val id = TempID(currency)
|
||||||
|
|
||||||
if (valuePredicate(id)) {
|
if (valuePredicate(id)) {
|
||||||
mutableMap.put(id, currency)
|
mutableMap[id] = currency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return destination
|
return destination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun Raise<Throwable>.saveAccount(account: Account.CryptoPortfolio) {
|
||||||
|
catch(
|
||||||
|
block = { accountsCRUDRepository.saveAccount(account) },
|
||||||
|
catch = ::raise,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun derivePublicKeys(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
currencies: List<CryptoCurrency>,
|
||||||
|
): Either<Throwable, Unit> = Either.catch {
|
||||||
|
derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun Raise<Throwable>.findToken(
|
private suspend fun Raise<Throwable>.findToken(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
networkId: String,
|
networkId: String,
|
||||||
|
|
@ -307,6 +316,12 @@ class ManageCryptoCurrenciesUseCase(
|
||||||
derivationPath = currency.network.derivationPath,
|
derivationPath = currency.network.derivationPath,
|
||||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
constructor(status: CryptoCurrencyStatus) : this(
|
||||||
|
networkId = status.currency.network.backendId,
|
||||||
|
derivationPath = status.currency.network.derivationPath,
|
||||||
|
contractAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ModifiedCurrencyList(
|
private data class ModifiedCurrencyList(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
package com.tangem.domain.account.status.usecase
|
||||||
|
|
||||||
|
import arrow.core.left
|
||||||
|
import arrow.core.right
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import com.tangem.domain.account.models.AccountList
|
||||||
|
import com.tangem.domain.account.models.AccountStatusList
|
||||||
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||||
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
|
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase.Error
|
||||||
|
import com.tangem.domain.core.utils.lceError
|
||||||
|
import com.tangem.domain.models.TokensSortType
|
||||||
|
import com.tangem.domain.models.TotalFiatBalance
|
||||||
|
import com.tangem.domain.models.account.Account
|
||||||
|
import com.tangem.domain.models.account.AccountId
|
||||||
|
import com.tangem.domain.models.account.AccountStatus
|
||||||
|
import com.tangem.domain.models.account.DerivationIndex
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
|
import com.tangem.domain.models.tokenlist.TokenList
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.referral.ReferralRepository
|
||||||
|
import com.tangem.domain.referral.ReferralStatus
|
||||||
|
import com.tangem.test.mock.MockAccounts.createAccountList
|
||||||
|
import io.mockk.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class ArchiveCryptoPortfolioUseCaseTest {
|
||||||
|
|
||||||
|
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
|
||||||
|
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||||
|
private val referralRepository: ReferralRepository = mockk(relaxUnitFun = true)
|
||||||
|
|
||||||
|
private val useCase = ArchiveCryptoPortfolioUseCase(
|
||||||
|
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||||
|
crudRepository = crudRepository,
|
||||||
|
referralRepository = referralRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun resetMocks() {
|
||||||
|
clearMocks(singleAccountStatusListSupplier, referralRepository, crudRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val accountList = createAccountList(activeAccounts = 2)
|
||||||
|
val archivingAccount = accountList.accounts.last()
|
||||||
|
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||||
|
|
||||||
|
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId = archivingAccount.accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Unit.right()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder {
|
||||||
|
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||||
|
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||||
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should archive if referral status is active but token is null`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val accountList = createAccountList(activeAccounts = 2)
|
||||||
|
val archivingAccount = accountList.accounts.last()
|
||||||
|
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||||
|
|
||||||
|
val referralStatus = ReferralStatus(isActive = true, token = null, address = null)
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId = archivingAccount.accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Unit.right()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder {
|
||||||
|
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||||
|
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||||
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should archive if referral status is active but token is absent`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val accountList = createAccountList(activeAccounts = 2)
|
||||||
|
val archivingAccount = accountList.accounts.last()
|
||||||
|
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||||
|
|
||||||
|
val token = ReferralStatus.Token(
|
||||||
|
networkId = "ethereum",
|
||||||
|
contractAddress = "0x1",
|
||||||
|
)
|
||||||
|
val referralStatus = ReferralStatus(isActive = true, token = token, address = "0xABC")
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId = archivingAccount.accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Unit.right()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder {
|
||||||
|
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||||
|
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||||
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke returns error if account contains referral token `() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val token = ReferralStatus.Token(
|
||||||
|
networkId = "ethereum",
|
||||||
|
contractAddress = "0x1",
|
||||||
|
)
|
||||||
|
val defaultAddress = "0xABC"
|
||||||
|
|
||||||
|
val cryptoCurrency = mockk<CryptoCurrency.Token> {
|
||||||
|
every { this@mockk.network.backendId } returns token.networkId
|
||||||
|
every { this@mockk.contractAddress } returns token.contractAddress!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val networkAddress = NetworkAddress.Single(
|
||||||
|
defaultAddress = NetworkAddress.Address(
|
||||||
|
value = defaultAddress,
|
||||||
|
type = NetworkAddress.Address.Type.Primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val cryptoCurrencyStatus = mockk<CryptoCurrencyStatus> {
|
||||||
|
every { this@mockk.currency } returns cryptoCurrency
|
||||||
|
every { this@mockk.value.networkAddress } returns networkAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
val accountList = createAccountList(activeAccounts = 2)
|
||||||
|
val archivingAccount = accountList.accounts.last()
|
||||||
|
val archivingAccountStatus = AccountStatus.CryptoPortfolio(
|
||||||
|
account = archivingAccount as Account.CryptoPortfolio,
|
||||||
|
tokenList = TokenList.Ungrouped(
|
||||||
|
totalFiatBalance = TotalFiatBalance.Failed,
|
||||||
|
sortedBy = TokensSortType.NONE,
|
||||||
|
currencies = listOf(cryptoCurrencyStatus),
|
||||||
|
),
|
||||||
|
priceChangeLce = Unit.lceError(),
|
||||||
|
)
|
||||||
|
val accountListStatus = with(accountList.toStatus()) {
|
||||||
|
copy(
|
||||||
|
accountStatuses = accountStatuses.filter { it.accountId != archivingAccount.accountId } +
|
||||||
|
archivingAccountStatus,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
(accountList - archivingAccount).getOrNull()!!
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountListStatus
|
||||||
|
|
||||||
|
val referralStatus = ReferralStatus(isActive = true, token = token, address = defaultAddress)
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId = archivingAccount.accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Error.ActiveReferralStatus.left()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder {
|
||||||
|
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||||
|
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should return error if supplier returns null`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val accountId = AccountId.forCryptoPortfolio(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||||
|
)
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns null
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder { singleAccountStatusListSupplier.getSyncOrNull(params) }
|
||||||
|
coVerify(inverse = true) {
|
||||||
|
referralRepository.getReferralStatus(any())
|
||||||
|
crudRepository.saveAccounts(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should return error if account not found`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val accountList = AccountList.empty(userWalletId)
|
||||||
|
val accountId = AccountId.forCryptoPortfolio(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
derivationIndex = DerivationIndex.Companion(1).getOrNull()!!,
|
||||||
|
)
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||||
|
|
||||||
|
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder { singleAccountStatusListSupplier.getSyncOrNull(params) }
|
||||||
|
|
||||||
|
coVerify(inverse = true) {
|
||||||
|
referralRepository.getReferralStatus(any())
|
||||||
|
crudRepository.saveAccounts(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||||
|
val accountList = createAccountList(activeAccounts = 2)
|
||||||
|
val archivingAccount = accountList.accounts.last()
|
||||||
|
val updatedAccountList = (accountList - archivingAccount).getOrNull()!!
|
||||||
|
|
||||||
|
val params = SingleAccountStatusListProducer.Params(userWalletId)
|
||||||
|
coEvery { singleAccountStatusListSupplier.getSyncOrNull(params) } returns accountList.toStatus()
|
||||||
|
|
||||||
|
val referralStatus = ReferralStatus(isActive = false, token = null, address = null)
|
||||||
|
coEvery { referralRepository.getReferralStatus(userWalletId.stringValue) } returns referralStatus
|
||||||
|
|
||||||
|
val exception = IllegalStateException("Save failed")
|
||||||
|
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = useCase(accountId = archivingAccount.accountId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = Error.DataOperationFailed(exception).left()
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
|
||||||
|
coVerifyOrder {
|
||||||
|
singleAccountStatusListSupplier.getSyncOrNull(params)
|
||||||
|
referralRepository.getReferralStatus(userWalletId.stringValue)
|
||||||
|
crudRepository.saveAccounts(updatedAccountList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AccountList.toStatus(): AccountStatusList {
|
||||||
|
return AccountStatusList(
|
||||||
|
userWalletId = Companion.userWalletId,
|
||||||
|
accountStatuses = accounts.map {
|
||||||
|
AccountStatus.CryptoPortfolio(
|
||||||
|
account = it as Account.CryptoPortfolio,
|
||||||
|
tokenList = TokenList.Empty,
|
||||||
|
priceChangeLce = Unit.lceError(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
totalAccounts = totalAccounts,
|
||||||
|
totalFiatBalance = TotalFiatBalance.Loading,
|
||||||
|
sortType = sortType,
|
||||||
|
groupType = groupType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val userWalletId = UserWalletId("011")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns null
|
} returns null
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -124,7 +124,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns emptySet()
|
} returns emptySet()
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -144,7 +144,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns setOf(networkStatus)
|
} returns setOf(networkStatus)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -169,7 +169,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +185,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns setOf(networkStatus)
|
} returns setOf(networkStatus)
|
||||||
coEvery {
|
coEvery {
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
|
|
@ -201,7 +201,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||||
)
|
)
|
||||||
|
|
@ -222,7 +222,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns setOf(networkStatus)
|
} returns setOf(networkStatus)
|
||||||
coEvery {
|
coEvery {
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
|
|
@ -238,7 +238,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||||
)
|
)
|
||||||
|
|
@ -257,7 +257,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
|
||||||
coEvery {
|
coEvery {
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
} returns setOf(networkStatus)
|
} returns setOf(networkStatus)
|
||||||
coEvery {
|
coEvery {
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
|
|
@ -274,7 +274,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
accountsCRUDRepository.getUserWalletsSync()
|
accountsCRUDRepository.getUserWalletsSync()
|
||||||
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
|
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId), 1000)
|
||||||
singleAccountListSupplier.getSyncOrNull(
|
singleAccountListSupplier.getSyncOrNull(
|
||||||
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.core.flow
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Flow] supplier
|
* [Flow] supplier
|
||||||
|
|
@ -16,8 +17,14 @@ interface FlowSupplier<Params : Any, Data : Any> {
|
||||||
/** Supply [Flow] by [params] */
|
/** Supply [Flow] by [params] */
|
||||||
operator fun invoke(params: Params): Flow<Data>
|
operator fun invoke(params: Params): Flow<Data>
|
||||||
|
|
||||||
/** Get first [Data] by [params] or null if [Flow] is empty */
|
/** Synchronously get first value or null from [Flow] within [timeMillis] */
|
||||||
suspend fun getSyncOrNull(params: Params): Data? {
|
suspend fun getSyncOrNull(params: Params, timeMillis: Long? = null): Data? {
|
||||||
return invoke(params).firstOrNull()
|
val block = suspend { invoke(params).firstOrNull() }
|
||||||
|
|
||||||
|
return if (timeMillis == null) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
withTimeoutOrNull(timeMillis) { block() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
domain/referral/.gitignore
vendored
Normal file
1
domain/referral/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
9
domain/referral/build.gradle.kts
Normal file
9
domain/referral/build.gradle.kts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.kotlin.jvm)
|
||||||
|
alias(deps.plugins.kotlin.serialization)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(deps.kotlin.serialization)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tangem.domain.referral
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository for handling referral-related operations.
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
interface ReferralRepository {
|
||||||
|
|
||||||
|
/** Retrieves the referral status for a given [userWalletId] */
|
||||||
|
suspend fun getReferralStatus(userWalletId: String): ReferralStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReferralStatus(
|
||||||
|
val isActive: Boolean,
|
||||||
|
val token: Token?,
|
||||||
|
val address: String?,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Token(
|
||||||
|
val networkId: String,
|
||||||
|
val contractAddress: String?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ import com.tangem.core.ui.message.DialogMessage
|
||||||
import com.tangem.core.ui.message.EventMessageAction
|
import com.tangem.core.ui.message.EventMessageAction
|
||||||
import com.tangem.core.ui.message.ToastMessage
|
import com.tangem.core.ui.message.ToastMessage
|
||||||
import com.tangem.domain.account.producer.SingleAccountProducer
|
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||||
|
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
|
||||||
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
||||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
|
|
||||||
import com.tangem.domain.models.PortfolioId
|
import com.tangem.domain.models.PortfolioId
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.domain.models.account.Account
|
||||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||||
|
|
@ -104,13 +104,14 @@ internal class AccountDetailsModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
|
private fun failedArchiveDialog(error: ArchiveCryptoPortfolioUseCase.Error) {
|
||||||
// todo account referral case
|
|
||||||
val titleRes = when (error) {
|
val titleRes = when (error) {
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountNotFound,
|
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountNotFound,
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
||||||
-> R.string.common_something_went_wrong
|
-> R.string.common_something_went_wrong
|
||||||
|
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
|
||||||
|
-> R.string.account_could_not_archive_referral_program_title
|
||||||
}
|
}
|
||||||
val messageRes = when (error) {
|
val messageRes = when (error) {
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountListRequirementsNotMet,
|
||||||
|
|
@ -118,6 +119,8 @@ internal class AccountDetailsModel @Inject constructor(
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
is ArchiveCryptoPortfolioUseCase.Error.CriticalTechError.AccountsNotCreated,
|
||||||
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
is ArchiveCryptoPortfolioUseCase.Error.DataOperationFailed,
|
||||||
-> R.string.account_could_not_archive
|
-> R.string.account_could_not_archive
|
||||||
|
is ArchiveCryptoPortfolioUseCase.Error.ActiveReferralStatus,
|
||||||
|
-> R.string.account_could_not_archive_referral_program_message
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialogMessage = DialogMessage(
|
val dialogMessage = DialogMessage(
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ dependencies {
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.domain.referral)
|
||||||
implementation(projects.features.referral.domain)
|
implementation(projects.features.referral.domain)
|
||||||
|
|
||||||
/** Libs */
|
/** Libs */
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" ?>
|
|
||||||
<SmellBaseline>
|
|
||||||
<ManuallySuppressedIssues/>
|
|
||||||
<CurrentIssues>
|
|
||||||
<ID>MultilineLambdaItParameter:ReferralConverter.kt$ExpectedAwardsConverter${ ExpectedAward( paymentDate = it.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(), amount = "${it.amount} ${it.currency}", ) }</ID>
|
|
||||||
</CurrentIssues>
|
|
||||||
</SmellBaseline>
|
|
||||||
|
|
@ -70,10 +70,10 @@ private class ExpectedAwardsConverter : Converter<ReferralResponse.ExpectedAward
|
||||||
override fun convert(value: ReferralResponse.ExpectedAwards): ExpectedAwards {
|
override fun convert(value: ReferralResponse.ExpectedAwards): ExpectedAwards {
|
||||||
return ExpectedAwards(
|
return ExpectedAwards(
|
||||||
numberOfWallets = value.numberOfWallets,
|
numberOfWallets = value.numberOfWallets,
|
||||||
expectedAwards = value.list.map {
|
expectedAwards = value.list.map { awardItem ->
|
||||||
ExpectedAward(
|
ExpectedAward(
|
||||||
paymentDate = it.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(),
|
paymentDate = awardItem.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(),
|
||||||
amount = "${it.amount} ${it.currency}",
|
amount = "${awardItem.amount} ${awardItem.currency}",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.models.account.DerivationIndex
|
import com.tangem.domain.models.account.DerivationIndex
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.referral.ReferralStatus
|
||||||
import com.tangem.feature.referral.converters.ReferralConverter
|
import com.tangem.feature.referral.converters.ReferralConverter
|
||||||
import com.tangem.feature.referral.domain.ReferralRepository
|
import com.tangem.feature.referral.domain.ReferralRepository
|
||||||
import com.tangem.feature.referral.domain.models.ReferralData
|
import com.tangem.feature.referral.domain.models.ReferralData
|
||||||
|
|
@ -21,13 +22,15 @@ import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
typealias ExternalReferralRepository = com.tangem.domain.referral.ReferralRepository
|
||||||
|
|
||||||
internal class ReferralRepositoryImpl @Inject constructor(
|
internal class ReferralRepositoryImpl @Inject constructor(
|
||||||
private val referralApi: TangemTechApi,
|
private val referralApi: TangemTechApi,
|
||||||
private val referralConverter: ReferralConverter,
|
private val referralConverter: ReferralConverter,
|
||||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||||
private val userWalletsStore: UserWalletsStore,
|
private val userWalletsStore: UserWalletsStore,
|
||||||
excludedBlockchains: ExcludedBlockchains,
|
excludedBlockchains: ExcludedBlockchains,
|
||||||
) : ReferralRepository {
|
) : ReferralRepository, ExternalReferralRepository {
|
||||||
|
|
||||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||||
|
|
||||||
|
|
@ -47,6 +50,21 @@ internal class ReferralRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getReferralStatus(userWalletId: String): ReferralStatus {
|
||||||
|
val data = getReferralData(userWalletId)
|
||||||
|
|
||||||
|
return ReferralStatus(
|
||||||
|
isActive = data is ReferralData.ParticipantData,
|
||||||
|
token = data.tokens.firstOrNull()?.let { tokenData ->
|
||||||
|
ReferralStatus.Token(
|
||||||
|
networkId = tokenData.networkId,
|
||||||
|
contractAddress = tokenData.contractAddress,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
address = (data as? ReferralData.ParticipantData)?.referral?.address,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean {
|
override suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean {
|
||||||
val storedReferralData = referralStatus[userWalletId.stringValue] ?: getReferralData(userWalletId.stringValue)
|
val storedReferralData = referralStatus[userWalletId.stringValue] ?: getReferralData(userWalletId.stringValue)
|
||||||
return storedReferralData is ReferralData.ParticipantData
|
return storedReferralData is ReferralData.ParticipantData
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.feature.referral.converters.ReferralConverter
|
import com.tangem.feature.referral.converters.ReferralConverter
|
||||||
|
import com.tangem.feature.referral.data.ExternalReferralRepository
|
||||||
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
||||||
import com.tangem.feature.referral.domain.ReferralRepository
|
import com.tangem.feature.referral.domain.ReferralRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -34,4 +35,22 @@ class ReferralRepositoryModule {
|
||||||
excludedBlockchains = excludedBlockchains,
|
excludedBlockchains = excludedBlockchains,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideExternalReferralRepository(
|
||||||
|
tangemTechApi: TangemTechApi,
|
||||||
|
referralConverter: ReferralConverter,
|
||||||
|
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||||
|
userWalletsStore: UserWalletsStore,
|
||||||
|
excludedBlockchains: ExcludedBlockchains,
|
||||||
|
): ExternalReferralRepository {
|
||||||
|
return ReferralRepositoryImpl(
|
||||||
|
referralApi = tangemTechApi,
|
||||||
|
referralConverter = referralConverter,
|
||||||
|
coroutineDispatcher = coroutineDispatcherProvider,
|
||||||
|
userWalletsStore = userWalletsStore,
|
||||||
|
excludedBlockchains = excludedBlockchains,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +360,7 @@ include(":domain:notifications")
|
||||||
include(":domain:notifications:models")
|
include(":domain:notifications:models")
|
||||||
include(":domain:express")
|
include(":domain:express")
|
||||||
include(":domain:express:models")
|
include(":domain:express:models")
|
||||||
|
include(":domain:referral")
|
||||||
include(":domain:swap")
|
include(":domain:swap")
|
||||||
include(":domain:swap:models")
|
include(":domain:swap:models")
|
||||||
include(":domain:wallet-manager")
|
include(":domain:wallet-manager")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue