Updated on 2026-08-14
This commit is contained in:
parent
bf3a15c114
commit
475bd04c09
19 changed files with 0 additions and 1772 deletions
|
|
@ -10,8 +10,6 @@ import com.orhanobut.logger.AndroidLogAdapter
|
|||
import com.orhanobut.logger.Logger
|
||||
import com.tangem.Log
|
||||
import com.tangem.LogFormat
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
|
|
@ -35,7 +33,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
|
|
@ -62,11 +59,6 @@ import com.tangem.tap.domain.tokens.UserTokensRepository
|
|||
import com.tangem.tap.domain.tokens.UserTokensStorageService
|
||||
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
|
||||
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
|
|
@ -93,32 +85,6 @@ lateinit var shopService: TangemShopService
|
|||
internal lateinit var userTokensRepository: UserTokensRepository
|
||||
internal lateinit var derivationsFinder: DerivationsFinder
|
||||
|
||||
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
|
||||
private val walletManagersRepository by lazy {
|
||||
WalletManagersRepository.provideDefaultImplementation(
|
||||
walletManagerFactory = WalletManagerFactory(
|
||||
config = store.state.globalState.configManager
|
||||
?.config
|
||||
?.blockchainSdkConfig
|
||||
?: BlockchainSdkConfig(),
|
||||
),
|
||||
)
|
||||
}
|
||||
private val walletAmountsRepository by lazy {
|
||||
WalletAmountsRepository.provideDefaultImplementation(
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
)
|
||||
}
|
||||
val walletStoresManager by lazy {
|
||||
WalletStoresManager.provideDefaultImplementation(
|
||||
userTokensRepository = userTokensRepository,
|
||||
walletStoresRepository = walletStoresRepository,
|
||||
walletManagersRepository = walletManagersRepository,
|
||||
walletAmountsRepository = walletAmountsRepository,
|
||||
appCurrencyProvider = { store.state.globalState.appCurrency },
|
||||
)
|
||||
}
|
||||
|
||||
@HiltAndroidApp
|
||||
internal class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
|
|
@ -263,7 +229,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
)
|
||||
appStateHolder.mainStore = store
|
||||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.core.TangemError
|
||||
|
||||
sealed class WalletStoresError(code: Int) : TangemError(code) {
|
||||
override val silent: Boolean
|
||||
get() = (cause as? TangemError)?.silent == true
|
||||
|
||||
override val messageResId: Int? = null
|
||||
override val message: String?
|
||||
get() = customMessage
|
||||
|
||||
class FetchFiatRatesError(
|
||||
currencies: List<String>,
|
||||
override val cause: Throwable?,
|
||||
) : WalletStoresError(code = 60011) {
|
||||
override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies"
|
||||
}
|
||||
|
||||
class UnknownBlockchain : WalletStoresError(code = 60012) {
|
||||
override var customMessage: String = "Unknown blockchain"
|
||||
}
|
||||
|
||||
object NoInternetConnection : WalletStoresError(code = 60013) {
|
||||
override var customMessage: String = "No internet connection"
|
||||
}
|
||||
|
||||
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(code = 60014) {
|
||||
override var customMessage: String = "Wallet manager can not be created for $blockchain"
|
||||
}
|
||||
|
||||
class UpdateWalletManagerTokensError(
|
||||
blockchain: Blockchain,
|
||||
override val cause: Throwable,
|
||||
) : WalletStoresError(code = 600015) {
|
||||
override var customMessage: String = "Unable to update wallet manager tokens for currency $blockchain: $cause"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores
|
||||
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletStoresManager {
|
||||
/**
|
||||
* Get all [WalletStoreModel]s updates
|
||||
*
|
||||
* @return [Flow] with map of [WalletStoreModel] list assigned by [UserWalletId]
|
||||
* */
|
||||
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
|
||||
|
||||
/**
|
||||
* Get [WalletStoreModel]s updates which associated with provided [UserWalletId]
|
||||
*
|
||||
* @param userWalletId [UserWalletId] of user wallet
|
||||
*
|
||||
* @return [Flow] with [WalletStoreModel] list
|
||||
* */
|
||||
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
|
||||
|
||||
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
|
||||
|
||||
/**
|
||||
* Delete [WalletStoreModel]s associated with provided [UserWalletId]s
|
||||
*
|
||||
* @param userWalletsIds [UserWalletId] list
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Clear all [WalletStoreModel]s
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Fetch wallet stores associated with provided [UserWallet]. Fetched [WalletStoreModel]s updates can be observed
|
||||
* with [get] and [getAll] methods
|
||||
*
|
||||
* @param userWallet [UserWallet] to fetch [WalletStoreModel]s
|
||||
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun fetch(userWallet: UserWallet, refresh: Boolean = false): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Fetch wallet stores associated with provided [UserWallet]s. Fetched [WalletStoreModel]s updates can be observed
|
||||
* with [get] and [getAll] methods
|
||||
*
|
||||
* @param userWallets [UserWallet]s list to fetch [WalletStoreModel]s
|
||||
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean = false): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Update [WalletStoreModel]s amounts associated with provided [UserWallet]s
|
||||
*
|
||||
* @param userWallets [UserWallet]s list to update [WalletStoreModel]s amounts
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit>
|
||||
|
||||
suspend fun updateSelectedAddress(
|
||||
userWalletId: UserWalletId,
|
||||
currency: Currency,
|
||||
addressType: AddressType,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
// For provider
|
||||
companion object
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.di
|
||||
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.implementation.DefaultWalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.implementation.DummyWalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
|
||||
fun WalletStoresManager.Companion.provideDummyImplementation(): WalletStoresManager {
|
||||
return DummyWalletStoresManager()
|
||||
}
|
||||
|
||||
fun WalletStoresManager.Companion.provideDefaultImplementation(
|
||||
userTokensRepository: UserTokensRepository,
|
||||
walletStoresRepository: WalletStoresRepository,
|
||||
walletAmountsRepository: WalletAmountsRepository,
|
||||
walletManagersRepository: WalletManagersRepository,
|
||||
appCurrencyProvider: () -> FiatCurrency,
|
||||
): WalletStoresManager {
|
||||
return DefaultWalletStoresManager(
|
||||
userTokensRepository = userTokensRepository,
|
||||
walletStoresRepository = walletStoresRepository,
|
||||
walletAmountsRepository = walletAmountsRepository,
|
||||
walletManagersRepository = walletManagersRepository,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.implementation
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.*
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.model.builders.WalletStoreBuilder
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresError
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateSelectedAddress
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultWalletStoresManager(
|
||||
private val userTokensRepository: UserTokensRepository,
|
||||
private val walletStoresRepository: WalletStoresRepository,
|
||||
private val walletAmountsRepository: WalletAmountsRepository,
|
||||
private val walletManagersRepository: WalletManagersRepository,
|
||||
private val appCurrencyProvider: () -> FiatCurrency,
|
||||
) : WalletStoresManager {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
|
||||
return walletStoresRepository.getAll()
|
||||
}
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
|
||||
return walletStoresRepository.get(userWalletId)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
|
||||
return walletStoresRepository.getSync(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return walletStoresRepository.delete(userWalletsIds)
|
||||
.flatMap { walletManagersRepository.delete(userWalletsIds) }
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return walletStoresRepository.clear()
|
||||
}
|
||||
|
||||
override suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean): CompletionResult<Unit> =
|
||||
withContext(Dispatchers.Default) {
|
||||
val fiatCurrency = appCurrencyProvider.invoke()
|
||||
val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency
|
||||
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
fiatCurrency = fiatCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
userWallets
|
||||
.mapNotNull { userWallet ->
|
||||
val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId)
|
||||
if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) {
|
||||
fetchWalletsIfNeeded(userWallet)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.fold(arrayListOf<UserWallet>()) { acc, data ->
|
||||
acc.apply { add(data) }
|
||||
}
|
||||
.flatMap {
|
||||
walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult<Unit> {
|
||||
return fetch(listOf(userWallet), refresh)
|
||||
}
|
||||
|
||||
override suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit> {
|
||||
val fiatCurrency = appCurrencyProvider.invoke()
|
||||
|
||||
return walletAmountsRepository.updateAmountsForUserWallets(userWallets, fiatCurrency)
|
||||
.doOnSuccess {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
fiatCurrency = fiatCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateSelectedAddress(
|
||||
userWalletId: UserWalletId,
|
||||
currency: Currency,
|
||||
addressType: AddressType,
|
||||
): CompletionResult<Unit> {
|
||||
return walletStoresRepository.update(userWalletId) { walletStores ->
|
||||
walletStores
|
||||
.firstOrNull {
|
||||
it.blockchain == currency.blockchain &&
|
||||
it.derivationPath?.rawPath == currency.derivationPath
|
||||
}
|
||||
?.updateSelectedAddress(currency, addressType)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult<UserWallet> {
|
||||
return if (userWallet.isMultiCurrency) {
|
||||
fetchMultiWallets(userWallet)
|
||||
} else {
|
||||
fetchSingleWallet(userWallet)
|
||||
}
|
||||
.map { userWallet }
|
||||
}
|
||||
|
||||
private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val derivationStyle = scanResponse.derivationStyleProvider.getDerivationStyle()
|
||||
val userTokens = withContext(Dispatchers.IO) {
|
||||
userTokensRepository.getUserTokens(scanResponse.card, derivationStyle)
|
||||
}
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
return withContext(Dispatchers.Default) {
|
||||
userTokens.toBlockchainNetworks()
|
||||
.also { blockchainNetworks ->
|
||||
walletStoresRepository.deleteDifference(
|
||||
userWalletId = userWalletId,
|
||||
currentBlockchains = blockchainNetworks.map {
|
||||
Currency.Blockchain(it.blockchain, it.derivationPath)
|
||||
},
|
||||
)
|
||||
}
|
||||
.map { blockchainNetwork ->
|
||||
val storeWalletStore: suspend (WalletManager?) -> CompletionResult<Unit> =
|
||||
{ walletManager ->
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
|
||||
.walletManager(walletManager)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
walletManagersRepository.findOrMakeMultiCurrencyWalletManager(
|
||||
userWallet = userWallet,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
)
|
||||
.flatMap { walletManager ->
|
||||
storeWalletStore(walletManager)
|
||||
}
|
||||
.flatMapOnFailure { error ->
|
||||
when (error) {
|
||||
is WalletStoresError.WalletManagerNotCreated,
|
||||
is WalletStoresError.UpdateWalletManagerTokensError,
|
||||
-> storeWalletStore(null)
|
||||
else -> CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.fold()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchSingleWallet(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return walletManagersRepository.findOrMakeSingleCurrencyWalletManager(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
.flatMap { walletManager ->
|
||||
val userWalletId = userWallet.walletId
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, walletManager)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
.flatMapOnFailure { error ->
|
||||
when (error) {
|
||||
is WalletStoresError.WalletManagerNotCreated,
|
||||
is WalletStoresError.UpdateWalletManagerTokensError,
|
||||
-> CompletionResult.Success(Unit)
|
||||
else -> CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class State(
|
||||
val fiatCurrency: FiatCurrency? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.implementation
|
||||
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
internal class DummyWalletStoresManager : WalletStoresManager {
|
||||
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
|
||||
return emptyFlow()
|
||||
}
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
|
||||
return emptyFlow()
|
||||
}
|
||||
|
||||
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun updateSelectedAddress(
|
||||
userWalletId: UserWalletId,
|
||||
currency: Currency,
|
||||
addressType: AddressType,
|
||||
): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
|
||||
interface WalletAmountsRepository {
|
||||
|
||||
/**
|
||||
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
|
||||
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
|
||||
* @param userWallets list of [UserWallet] which will be used to get the list of associated [WalletStoreModel]
|
||||
* @param fiatCurrency current app [FiatCurrency]
|
||||
* */
|
||||
suspend fun updateAmountsForUserWallets(
|
||||
userWallets: List<UserWallet>,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
|
||||
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
|
||||
* @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel]
|
||||
* @param fiatCurrency current app [FiatCurrency]
|
||||
* */
|
||||
suspend fun updateAmountsForUserWallet(userWallet: UserWallet, fiatCurrency: FiatCurrency): CompletionResult<Unit>
|
||||
|
||||
suspend fun updateAmountsForWalletStores(
|
||||
walletStores: List<WalletStoreModel>,
|
||||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage]
|
||||
* and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data
|
||||
* @param walletStore [WalletStoreModel] to update
|
||||
* @param userWallet [UserWallet] associated with provided [walletStore]
|
||||
* @param fiatCurrency current app [FiatCurrency]
|
||||
* */
|
||||
suspend fun updateAmountsForWalletStore(
|
||||
walletStore: WalletStoreModel,
|
||||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletStoresRepository {
|
||||
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
|
||||
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
|
||||
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
|
||||
|
||||
suspend fun contains(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
|
||||
suspend fun deleteDifference(
|
||||
userWalletId: UserWalletId,
|
||||
currentBlockchains: List<Currency.Blockchain>,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
suspend fun storeOrUpdate(userWalletId: UserWalletId, walletStore: WalletStoreModel): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
* Updates [WalletStoreModel] in storage for user wallet with provided [UserWalletId]
|
||||
*
|
||||
* @param userWalletId [UserWalletId] of user wallet
|
||||
* @param operation Lambda which receives list of [WalletStoreModel] assigned to user wallet with [userWalletId]
|
||||
* and returns updated [WalletStoreModel]. If null returned then do nothing
|
||||
*
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
operation: (List<WalletStoreModel>) -> WalletStoreModel?,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.di
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletStoresRepository
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
||||
fun WalletStoresRepository.Companion.provideDefaultImplementation(): WalletStoresRepository {
|
||||
return DefaultWalletStoresRepository()
|
||||
}
|
||||
|
||||
fun WalletManagersRepository.Companion.provideDefaultImplementation(
|
||||
walletManagerFactory: WalletManagerFactory,
|
||||
): WalletManagersRepository {
|
||||
return DefaultWalletManagersRepository(walletManagerFactory)
|
||||
}
|
||||
|
||||
fun WalletAmountsRepository.Companion.provideDefaultImplementation(
|
||||
tangemTechService: TangemTechService,
|
||||
): WalletAmountsRepository {
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
return DefaultWalletAmountsRepository(
|
||||
tangemTechApi = tangemTechService.api,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.implementation
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.extensions.Result.Failure
|
||||
import com.tangem.blockchain.extensions.Result.Success
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.common.util.hasDerivation
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresError
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.utils.*
|
||||
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
|
||||
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.filterByCoin
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultWalletAmountsRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAmountsRepository {
|
||||
|
||||
override suspend fun updateAmountsForUserWallets(
|
||||
userWallets: List<UserWallet>,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return if (userWallets.isEmpty()) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
awaitAll(
|
||||
async { fetchAmountsForUserWallets(userWallets) },
|
||||
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
||||
).fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAmountsForUserWallet(
|
||||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency)
|
||||
}
|
||||
|
||||
override suspend fun updateAmountsForWalletStores(
|
||||
walletStores: List<WalletStoreModel>,
|
||||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return if (walletStores.isEmpty()) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
val userWalletId = userWallet.walletId
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
awaitAll(
|
||||
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
||||
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
||||
).fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAmountsForWalletStore(
|
||||
walletStore: WalletStoreModel,
|
||||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency)
|
||||
}
|
||||
|
||||
private suspend fun fetchFiatRates(
|
||||
userWallets: List<UserWallet>,
|
||||
walletStores: List<WalletStoreModel>?,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
return CompletionResult.Failure(WalletStoresError.NoInternetConnection)
|
||||
}
|
||||
|
||||
val walletStoresInternal = walletStores ?: getWalletStores(userWallets)
|
||||
val currencies = walletStoresInternal.asSequence().flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList()
|
||||
|
||||
return withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
tangemTechApi.getRates(
|
||||
fiatCurrency.code.lowercase(),
|
||||
coinsIds.joinToString(","),
|
||||
)
|
||||
}.onSuccess {
|
||||
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
|
||||
return@withContext CompletionResult.Success(Unit)
|
||||
}.onFailure {
|
||||
val error = WalletStoresError.FetchFiatRatesError(
|
||||
currencies = currencies.map(Currency::currencySymbol).toList(),
|
||||
cause = it,
|
||||
)
|
||||
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Unable to fetch fiat rates
|
||||
|- Coins ids: $coinsIds
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
return@withContext CompletionResult.Failure(error)
|
||||
}
|
||||
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountsForUserWallets(userWallets: List<UserWallet>): CompletionResult<Unit> =
|
||||
withContext(Dispatchers.Default) {
|
||||
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountsForUserWallet(userWallet: UserWallet): CompletionResult<Unit> =
|
||||
withContext(Dispatchers.Default) {
|
||||
val userWalletId = userWallet.walletId
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val walletStores = getWalletStores(listOf(userWallet))
|
||||
|
||||
fetchAmountForWalletStores(userWalletId, scanResponse, walletStores)
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountForWalletStores(
|
||||
userWalletId: UserWalletId,
|
||||
scanResponse: ScanResponse,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): CompletionResult<Unit> = coroutineScope {
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
walletStores.forEach {
|
||||
updateWalletStoreWithUnreachable(it)
|
||||
}
|
||||
return@coroutineScope CompletionResult.Failure(WalletStoresError.NoInternetConnection)
|
||||
}
|
||||
|
||||
walletStores.map { walletStore ->
|
||||
async {
|
||||
// TODO: Find wallet manager via [com.tangem.domain.wallets.legacy.WalletManagersRepository]
|
||||
val walletManager = walletStore.walletManager
|
||||
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
|
||||
}
|
||||
}.awaitAll().fold()
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountsForWalletStore(
|
||||
userWalletId: UserWalletId,
|
||||
scanResponse: ScanResponse,
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager?,
|
||||
): CompletionResult<Unit> {
|
||||
val isDerivationMissed = with(walletStore) {
|
||||
derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath)
|
||||
}
|
||||
|
||||
return when {
|
||||
isDerivationMissed -> {
|
||||
updateWalletStoreWithMissedDerivation(walletStore)
|
||||
}
|
||||
walletManager == null -> {
|
||||
updateWalletStoreWithUnreachable(walletStore)
|
||||
}
|
||||
else -> {
|
||||
updateWalletManager(scanResponse, walletManager)
|
||||
.map {
|
||||
updateWalletManagerInStorage(userWalletId, walletManager)
|
||||
}
|
||||
.flatMap {
|
||||
updateWalletStoreWithAmounts(
|
||||
walletStore = walletStore,
|
||||
updatedWallet = walletManager.wallet,
|
||||
// FIXME: move DemoHelper to Demo core module maybe
|
||||
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
|
||||
)
|
||||
}
|
||||
.flatMap {
|
||||
fetchWalletStoreRentIfNeeded(walletStore, walletManager)
|
||||
}
|
||||
.flatMapOnFailure { error ->
|
||||
updateWalletStoreWithError(
|
||||
walletStore = walletStore,
|
||||
wallet = walletManager.wallet,
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
walletManager: WalletManager,
|
||||
demoCardsDelay: Duration = with(Duration) { 500.milliseconds },
|
||||
): CompletionResult<Unit> = catching {
|
||||
if (scanResponse.isDemoCard() || TestActions.testAmountInjectionForWalletManagerEnabled) {
|
||||
delay(demoCardsDelay)
|
||||
TestActions.testAmountInjectionForWalletManagerEnabled = false
|
||||
} else {
|
||||
walletManager.update()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchWalletStoreRentIfNeeded(
|
||||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
): CompletionResult<Unit> {
|
||||
val rentProvider = walletManager as? RentProvider ?: return CompletionResult.Success(Unit)
|
||||
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is Success -> {
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs = walletManager.wallet.getPendingTransactions(
|
||||
PendingTransactionType.Outgoing,
|
||||
).filterByCoin()
|
||||
|
||||
val rentExempt = result.data
|
||||
val setRent = if (outgoingTxs.isEmpty()) {
|
||||
balance < rentExempt
|
||||
} else {
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amountValue ?: BigDecimal.ZERO }
|
||||
val rest = balance.minus(outgoingAmount)
|
||||
balance < rest
|
||||
}
|
||||
|
||||
updateWalletStoreWithRent(
|
||||
walletStore = walletStore,
|
||||
rent = if (setRent) {
|
||||
WalletStoreModel.WalletRent(
|
||||
rent = rentProvider.rentAmount(),
|
||||
exemptionAmount = rentExempt,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
is Failure -> Unit
|
||||
}
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoreWithError(walletStore: WalletStoreModel, wallet: Wallet, error: TangemError) =
|
||||
withContext(Dispatchers.Default) {
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Unable to fetch amounts
|
||||
|- User wallet id: ${walletStore.userWalletId}
|
||||
|- Blockchain: ${walletStore.blockchain}
|
||||
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
if (error is BlockchainSdkError) {
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
it.updateWithError(
|
||||
wallet = wallet,
|
||||
error = error,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoreWithAmounts(
|
||||
walletStore: WalletStoreModel,
|
||||
updatedWallet: Wallet,
|
||||
isDemo: Boolean,
|
||||
) = withContext(Dispatchers.Default) {
|
||||
Timber.d(
|
||||
"""
|
||||
Fetched amounts
|
||||
|- User wallet id: ${walletStore.userWalletId}
|
||||
|- Blockchain: ${walletStore.blockchain}
|
||||
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
if (isDemo) {
|
||||
it.updateWithDemoAmounts(wallet = updatedWallet)
|
||||
} else {
|
||||
it.updateWithAmounts(wallet = updatedWallet)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoreWithMissedDerivation(walletStore: WalletStoreModel) =
|
||||
withContext(Dispatchers.Default) {
|
||||
Timber.e(
|
||||
"""
|
||||
Missed derivation
|
||||
|- User wallet id: ${walletStore.userWalletId}
|
||||
|- Blockchain: ${walletStore.blockchain}
|
||||
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
it.updateWithMissedDerivation()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoreWithUnreachable(walletStore: WalletStoreModel) =
|
||||
withContext(Dispatchers.Default) {
|
||||
Timber.e(
|
||||
"""
|
||||
Wallet manager is null
|
||||
|- User wallet id: ${walletStore.userWalletId}
|
||||
|- Blockchain: ${walletStore.blockchain}
|
||||
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
it.updateWithUnreachable()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoresWithFiatRates(
|
||||
walletStores: List<WalletStoreModel>,
|
||||
fiatRates: Map<String, Double>,
|
||||
) = withContext(Dispatchers.Default) {
|
||||
Timber.d(
|
||||
"""
|
||||
Fetched fiat rates
|
||||
|- User wallets ids: ${walletStores.map { it.userWalletId }.distinct()}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStores(
|
||||
walletStoresToUpdate = walletStores,
|
||||
update = {
|
||||
it.updateWithFiatRates(rates = fiatRates)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateWalletStoreWithRent(walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?) =
|
||||
withContext(Dispatchers.Default) {
|
||||
Timber.d(
|
||||
"""
|
||||
Fetched wallet rent
|
||||
|- User wallet id: ${walletStore.userWalletId}
|
||||
|- Blockchain: ${walletStore.blockchain}
|
||||
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|
||||
|- Rent: $rent
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
if (rent != walletStore.walletRent) {
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
it.updateWithRent(rent)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateWalletManagerInStorage(userWalletId: UserWalletId, walletManager: WalletManager) =
|
||||
withContext(Dispatchers.Default) {
|
||||
WalletManagerStorage.update { prevManagers ->
|
||||
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
|
||||
.addOrReplace(walletManager) {
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||
}
|
||||
|
||||
prevManagers.apply {
|
||||
set(userWalletId, newManagersForUserWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
|
||||
return userWallets.map { it.walletId }.flatMap { userWalletId ->
|
||||
WalletStoresStorage.getAll().firstOrNull()?.get(userWalletId).orEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.implementation
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.mapFailure
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.makeWalletManagerForApp
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.WalletManagersRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresError
|
||||
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultWalletManagersRepository(
|
||||
private val walletManagerFactory: WalletManagerFactory,
|
||||
) : WalletManagersRepository {
|
||||
private val walletManagersStorage = WalletManagerStorage
|
||||
|
||||
override suspend fun findOrMakeMultiCurrencyWalletManager(
|
||||
userWallet: UserWallet,
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
): CompletionResult<WalletManager> {
|
||||
return findOrMakeInternal(userWallet, blockchainNetwork)
|
||||
}
|
||||
|
||||
override suspend fun findOrMakeSingleCurrencyWalletManager(
|
||||
userWallet: UserWallet,
|
||||
): CompletionResult<WalletManager> {
|
||||
return findOrMakeInternal(userWallet, blockchainNetwork = null)
|
||||
}
|
||||
|
||||
private suspend fun findOrMakeInternal(
|
||||
userWallet: UserWallet,
|
||||
blockchainNetwork: BlockchainNetwork?,
|
||||
): CompletionResult<WalletManager> = withContext(Dispatchers.Default) {
|
||||
val foundWalletManager = findWalletManager(
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainNetwork?.blockchain,
|
||||
derivationPath = blockchainNetwork?.derivationPath,
|
||||
)
|
||||
|
||||
foundWalletManager?.updateTokens(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
)
|
||||
?: makeAndStore(userWallet, blockchainNetwork)
|
||||
}
|
||||
|
||||
private suspend fun makeAndStore(
|
||||
userWallet: UserWallet,
|
||||
blockchainNetwork: BlockchainNetwork?,
|
||||
): CompletionResult<WalletManager> {
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val blockchain = blockchainNetwork?.blockchain
|
||||
?: scanResponse.cardTypesResolver.getBlockchain().let { blockchain ->
|
||||
if (scanResponse.card.isTestCard) blockchain.getTestnetVersion() else blockchain
|
||||
}
|
||||
val derivationParams = getDerivationParams(
|
||||
derivationPath = blockchainNetwork?.derivationPath,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)
|
||||
|
||||
val walletManager = blockchain?.let {
|
||||
walletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
blockchain = blockchain,
|
||||
derivationParams = derivationParams,
|
||||
)
|
||||
}
|
||||
|
||||
return when {
|
||||
blockchain == Blockchain.Unknown || blockchain == null -> {
|
||||
val error = WalletStoresError.UnknownBlockchain()
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Unknown blockchain while creating wallet manager
|
||||
|- User wallet ID: ${userWallet.walletId}
|
||||
""".trimIndent(),
|
||||
)
|
||||
CompletionResult.Failure(error)
|
||||
}
|
||||
walletManager != null -> {
|
||||
walletManager.updateTokens(
|
||||
scanResponse = scanResponse,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
)
|
||||
.doOnSuccess { store(userWallet.walletId, it) }
|
||||
}
|
||||
else -> {
|
||||
val error = WalletStoresError.WalletManagerNotCreated(blockchain)
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Unable to create wallet manager
|
||||
|- User wallet ID: ${userWallet.walletId}
|
||||
|- Blockchain: $blockchain
|
||||
|- Derivation path: ${blockchainNetwork?.derivationPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> = catching {
|
||||
walletManagersStorage.update { prevManagers ->
|
||||
prevManagers.filterKeys { it !in userWalletIds } as HashMap<UserWalletId, List<WalletManager>>
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletId: UserWalletId, blockchain: Blockchain): CompletionResult<Unit> = catching {
|
||||
deleteInternal(userWalletId, blockchain)
|
||||
}
|
||||
|
||||
private suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
|
||||
walletManagersStorage.update { prevManagers ->
|
||||
prevManagers.apply {
|
||||
set(
|
||||
key = userWalletId,
|
||||
value = this[userWalletId].orEmpty() + walletManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteInternal(userWalletId: UserWalletId, blockchain: Blockchain?) {
|
||||
walletManagersStorage.update { prevManagers ->
|
||||
prevManagers.apply {
|
||||
if (blockchain == null) {
|
||||
set(
|
||||
key = userWalletId,
|
||||
value = emptyList(),
|
||||
)
|
||||
} else {
|
||||
set(
|
||||
key = userWalletId,
|
||||
value = this[userWalletId]
|
||||
?.filter { it.wallet.blockchain == blockchain }
|
||||
.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun WalletManager.updateTokens(
|
||||
scanResponse: ScanResponse,
|
||||
blockchainNetwork: BlockchainNetwork?,
|
||||
): CompletionResult<WalletManager> {
|
||||
val walletManager = this
|
||||
return catching {
|
||||
val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.cardTypesResolver.getPrimaryToken())
|
||||
|
||||
if (tokens != walletManager.cardTokens) {
|
||||
// TODO: remove ability to manipulate with walletManager.cardTokens
|
||||
walletManager.cardTokens.clear()
|
||||
walletManager.wallet.removeAllTokens()
|
||||
if (tokens.isNotEmpty()) {
|
||||
walletManager.cardTokens.addAll(tokens)
|
||||
// add empty amounts to prepare templates of tokens WalletDataModel
|
||||
// see: WalletMangerWalletStoreBuilderImpl.build()
|
||||
tokens.forEach { walletManager.wallet.setAmount(Amount(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
walletManager
|
||||
}
|
||||
.mapFailure {
|
||||
val error = WalletStoresError.UpdateWalletManagerTokensError(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
cause = it,
|
||||
)
|
||||
Timber.e(error)
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findWalletManager(
|
||||
userWalletId: UserWalletId,
|
||||
blockchain: Blockchain?,
|
||||
derivationPath: String?,
|
||||
): WalletManager? {
|
||||
return walletManagersStorage.getAll()
|
||||
.firstOrNull()
|
||||
?.get(userWalletId)
|
||||
?.let { userWalletManagers ->
|
||||
if (blockchain == null) {
|
||||
userWalletManagers.firstOrNull()
|
||||
} else {
|
||||
userWalletManagers.firstOrNull {
|
||||
it.wallet.blockchain == blockchain &&
|
||||
it.wallet.publicKey.derivationPath?.rawPath == derivationPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivationParams(
|
||||
derivationPath: String?,
|
||||
derivationStyleProvider: DerivationStyleProvider,
|
||||
): DerivationParams? {
|
||||
val derivationStyle = derivationStyleProvider.getDerivationStyle() ?: return null
|
||||
|
||||
return if (derivationPath == null) {
|
||||
DerivationParams.Default(derivationStyle)
|
||||
} else {
|
||||
DerivationParams.Custom(DerivationPath(derivationPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.utils.isSameWalletStore
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore
|
||||
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithSelf
|
||||
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultWalletStoresRepository : WalletStoresRepository {
|
||||
private val walletStoresStorage = WalletStoresStorage
|
||||
|
||||
override fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>> {
|
||||
return walletStoresStorage.getAll()
|
||||
}
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>> {
|
||||
return getAll().map { it[userWalletId].orEmpty() }
|
||||
}
|
||||
|
||||
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
|
||||
return get(userWalletId).firstOrNull() ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun contains(userWalletId: UserWalletId): Boolean {
|
||||
return getSync(userWalletId).isNotEmpty()
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> = catching {
|
||||
walletStoresStorage.update { prevStores ->
|
||||
prevStores.filterKeys { it !in userWalletsIds } as HashMap<UserWalletId, List<WalletStoreModel>>
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteDifference(
|
||||
userWalletId: UserWalletId,
|
||||
currentBlockchains: List<Currency.Blockchain>,
|
||||
): CompletionResult<Unit> = catching {
|
||||
if (currentBlockchains != getSync(userWalletId)) {
|
||||
walletStoresStorage.update { prevStores ->
|
||||
prevStores.apply {
|
||||
this[userWalletId] = this[userWalletId]
|
||||
?.filter { it.blockchainWalletData.currency in currentBlockchains }
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> = catching {
|
||||
walletStoresStorage.update { hashMapOf() }
|
||||
}
|
||||
|
||||
override suspend fun storeOrUpdate(
|
||||
userWalletId: UserWalletId,
|
||||
walletStore: WalletStoreModel,
|
||||
): CompletionResult<Unit> = catching {
|
||||
walletStoresStorage.update { prevStores ->
|
||||
prevStores.addOrUpdate(userWalletId, walletStore)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
operation: (List<WalletStoreModel>) -> WalletStoreModel?,
|
||||
): CompletionResult<Unit> = catching {
|
||||
val walletStores = getSync(userWalletId).toMutableList()
|
||||
val updatedWalletStore = operation(walletStores) ?: return CompletionResult.Success(Unit)
|
||||
|
||||
val index = walletStores.indexOfFirst { it.isSameWalletStore(updatedWalletStore) }
|
||||
if (index == -1 || updatedWalletStore == walletStores[index]) return CompletionResult.Success(Unit)
|
||||
|
||||
walletStores[index] = updatedWalletStore
|
||||
|
||||
walletStoresStorage.update { prevStores ->
|
||||
prevStores.apply {
|
||||
this[userWalletId] = walletStores
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun HashMap<UserWalletId, List<WalletStoreModel>>.addOrUpdate(
|
||||
userWalletId: UserWalletId,
|
||||
walletStore: WalletStoreModel,
|
||||
): HashMap<UserWalletId, List<WalletStoreModel>> = withContext(Dispatchers.Default) {
|
||||
val currentWalletStores = this@addOrUpdate
|
||||
val userWalletStores = currentWalletStores[userWalletId]
|
||||
|
||||
if (userWalletStores.isNullOrEmpty()) {
|
||||
currentWalletStores.apply {
|
||||
set(userWalletId, listOf(walletStore))
|
||||
}
|
||||
} else {
|
||||
val currentWalletStore = userWalletStores.find(walletStore::isSameWalletStore)
|
||||
if (currentWalletStore == null) {
|
||||
currentWalletStores.apply {
|
||||
set(userWalletId, userWalletStores + walletStore)
|
||||
}
|
||||
} else {
|
||||
currentWalletStores.replaceWalletStore(
|
||||
walletStoreToUpdate = currentWalletStore,
|
||||
update = { it.updateWithSelf(walletStore) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.implementation.utils
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.common.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal fun WalletDataModel.updateWithFiatRate(fiatRate: BigDecimal?): WalletDataModel {
|
||||
return this.copy(
|
||||
fiatRate = fiatRate,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithFiatRates(fiatRates: Map<String, Double>): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
val rate = fiatRates[walletData.currency.coinId]?.toBigDecimal()
|
||||
walletData.updateWithFiatRate(rate)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithAmounts(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithAmount(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel {
|
||||
val pendingTransactions = wallet.getPendingTransactions()
|
||||
return this.copy(
|
||||
status = when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val amount = wallet.fundsAvailable(AmountType.Coin)
|
||||
if (pendingTransactions.isEmpty()) {
|
||||
WalletDataModel.VerifiedOnline(amount)
|
||||
} else {
|
||||
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
}
|
||||
|
||||
is Currency.Token -> {
|
||||
val token = currency.token
|
||||
val amount = wallet.fundsAvailable(AmountType.Token(token))
|
||||
val hasTokenPendingTransactions = pendingTransactions.any {
|
||||
it.transactionData.amount.currencySymbol == token.symbol
|
||||
}
|
||||
|
||||
when {
|
||||
hasTokenPendingTransactions -> {
|
||||
WalletDataModel.TransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
|
||||
pendingTransactions.isNotEmpty() -> {
|
||||
// FIXME: Necessary to avoid passing pending transactions
|
||||
// because SameCurrencyTransactionInProgress didn't use it. It used only to define main button
|
||||
// availability. UI layer turned off pending transaction visibility for this state.
|
||||
WalletDataModel.SameCurrencyTransactionInProgress(amount, pendingTransactions)
|
||||
}
|
||||
else -> {
|
||||
WalletDataModel.VerifiedOnline(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithDemoAmount(wallet: Wallet): WalletDataModel {
|
||||
val amount = DemoHelper.config.getBalance(wallet.blockchain)
|
||||
wallet.setAmount(amount)
|
||||
return this.copy(
|
||||
status = WalletDataModel.VerifiedOnline(amount = amount.value ?: BigDecimal.ZERO),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithDemoAmounts(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithDemoAmount(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithError(wallet: Wallet, error: TangemError): WalletDataModel {
|
||||
return this.copy(
|
||||
status = when (error) {
|
||||
is BlockchainSdkError.AccountNotFound -> {
|
||||
val amountToCreateAccount = wallet.blockchain
|
||||
.amountToCreateAccount(wallet.getFirstToken())
|
||||
|
||||
if (amountToCreateAccount != null) {
|
||||
WalletDataModel.NoAccount(
|
||||
amountToCreateAccount = amountToCreateAccount,
|
||||
)
|
||||
} else {
|
||||
WalletDataModel.Unreachable(
|
||||
errorMessage = error.customMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> WalletDataModel.Unreachable(
|
||||
errorMessage = error.customMessage,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithError(wallet: Wallet, error: TangemError): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithError(wallet, error)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithSelf(newWalletData: WalletDataModel): WalletDataModel {
|
||||
val oldWalletData = this
|
||||
val oldStatus = oldWalletData.status
|
||||
return oldWalletData.copy(
|
||||
status = when (val newStatus = newWalletData.status) {
|
||||
is WalletDataModel.Loading -> when (oldStatus) {
|
||||
is WalletDataModel.MissedDerivation -> WalletDataModel.Loading
|
||||
else -> oldStatus
|
||||
}
|
||||
is WalletDataModel.MissedDerivation,
|
||||
is WalletDataModel.NoAccount,
|
||||
is WalletDataModel.Unreachable,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
-> newStatus
|
||||
},
|
||||
existentialDeposit = newWalletData.existentialDeposit,
|
||||
walletAddresses = newWalletData.walletAddresses?.copy(
|
||||
selectedAddress = oldWalletData.walletAddresses?.selectedAddress
|
||||
?: newWalletData.walletAddresses.selectedAddress,
|
||||
),
|
||||
fiatRate = newWalletData.fiatRate ?: oldWalletData.fiatRate,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithMissedDerivation(): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.copy(
|
||||
status = WalletDataModel.MissedDerivation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithUnreachable(): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.copy(
|
||||
status = WalletDataModel.Unreachable(
|
||||
errorMessage = null,
|
||||
amount = walletData.status.amount,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithSelf(newWalletsData: List<WalletDataModel>): List<WalletDataModel> {
|
||||
val oldWalletsData = this
|
||||
val updatedWalletsData = arrayListOf<WalletDataModel>()
|
||||
|
||||
newWalletsData.forEach { newWalletData ->
|
||||
val walletDataToUpdate = oldWalletsData.firstOrNull(newWalletData::isSameWalletData)
|
||||
if (walletDataToUpdate != null) {
|
||||
updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData))
|
||||
} else {
|
||||
updatedWalletsData.add(newWalletData)
|
||||
}
|
||||
}
|
||||
|
||||
return updatedWalletsData
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateSelectedAddress(
|
||||
currency: Currency,
|
||||
addressType: AddressType,
|
||||
): List<WalletDataModel> {
|
||||
val index = this.indexOfFirst { it.currency == currency }
|
||||
if (index == -1) return this
|
||||
|
||||
val oldWalletData = this[index]
|
||||
val updatedWalletData = oldWalletData.updateSelectedAddress(addressType)
|
||||
if (oldWalletData == updatedWalletData) return this
|
||||
|
||||
return this.toMutableList().apply {
|
||||
this[index] = updatedWalletData
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateSelectedAddress(addressType: AddressType): WalletDataModel {
|
||||
val addresses = walletAddresses ?: return this
|
||||
val selectedAddress = addresses.list
|
||||
.firstOrNull { it.type == addressType }
|
||||
?: addresses.selectedAddress
|
||||
|
||||
return this.copy(
|
||||
walletAddresses = addresses.copy(
|
||||
selectedAddress = selectedAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean {
|
||||
return this.currency == other.currency
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.repository.implementation.utils
|
||||
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import timber.log.Timber
|
||||
|
||||
internal inline fun HashMap<UserWalletId, List<WalletStoreModel>>.replaceWalletStore(
|
||||
walletStoreToUpdate: WalletStoreModel,
|
||||
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
|
||||
): HashMap<UserWalletId, List<WalletStoreModel>> {
|
||||
return replaceWalletStores(listOf(walletStoreToUpdate), update)
|
||||
}
|
||||
|
||||
internal inline fun HashMap<UserWalletId, List<WalletStoreModel>>.replaceWalletStores(
|
||||
walletStoresToUpdate: List<WalletStoreModel>,
|
||||
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
|
||||
): HashMap<UserWalletId, List<WalletStoreModel>> {
|
||||
return this.apply {
|
||||
val currentWalletStores = this
|
||||
walletStoresToUpdate
|
||||
.groupBy { it.userWalletId }
|
||||
.forEach { (userWalletId, walletStoresToUpdate) ->
|
||||
currentWalletStores[userWalletId] = currentWalletStores[userWalletId]
|
||||
?.replaceWalletStores(walletStoresToUpdate, update)
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithError(wallet: Wallet, error: TangemError): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithError(
|
||||
wallet = wallet,
|
||||
error = error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithAmounts(wallet: Wallet): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithAmounts(wallet = wallet),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithDemoAmounts(wallet: Wallet): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithDemoAmounts(wallet = wallet),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithFiatRates(rates: Map<String, Double>): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithFiatRates(rates),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithSelf(newWalletStore: WalletStoreModel): WalletStoreModel {
|
||||
val oldStore = this
|
||||
return oldStore.copy(
|
||||
derivationPath = newWalletStore.derivationPath,
|
||||
walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData),
|
||||
walletRent = newWalletStore.walletRent,
|
||||
blockchainNetwork = newWalletStore.blockchainNetwork,
|
||||
walletManager = newWalletStore.walletManager,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithMissedDerivation(): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithMissedDerivation(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithUnreachable(): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateWithUnreachable(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletRent = rent,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.updateSelectedAddress(currency: Currency, addressType: AddressType): WalletStoreModel {
|
||||
return this.copy(
|
||||
walletsData = walletsData.updateSelectedAddress(currency, addressType),
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun List<WalletStoreModel>.replaceWalletStores(
|
||||
walletStoresToUpdate: List<WalletStoreModel>,
|
||||
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
|
||||
): List<WalletStoreModel> {
|
||||
val mutableStores = ArrayList<WalletStoreModel>(this)
|
||||
|
||||
walletStoresToUpdate.forEach { walletStoreToUpdate ->
|
||||
val index = mutableStores.indexOfFirst(walletStoreToUpdate::isSameWalletStore)
|
||||
// Can be possible if user hides wallet store when it's tokens is loading
|
||||
if (index == -1) return@forEach
|
||||
|
||||
val currentWalletStore = mutableStores[index]
|
||||
val updatedWalletStore = update(currentWalletStore)
|
||||
|
||||
if (currentWalletStore != updatedWalletStore) {
|
||||
Timber.d(
|
||||
"""
|
||||
Update wallet store in storage
|
||||
|- User wallet ID: ${updatedWalletStore.userWalletId}
|
||||
|- Blockchain: ${updatedWalletStore.blockchain}
|
||||
|- Derivation path: ${updatedWalletStore.derivationPath?.rawPath}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
mutableStores[index] = updatedWalletStore
|
||||
}
|
||||
}
|
||||
|
||||
return mutableStores
|
||||
}
|
||||
|
||||
internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean {
|
||||
return this.userWalletId == other.userWalletId &&
|
||||
this.blockchain == other.blockchain &&
|
||||
this.derivationPath == other.derivationPath
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.storage
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal object WalletManagerStorage {
|
||||
private val managers = MutableSharedFlow<HashMap<UserWalletId, List<WalletManager>>>(replay = 1)
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
managers.tryEmit(hashMapOf())
|
||||
}
|
||||
|
||||
fun getAll(): SharedFlow<Map<UserWalletId, List<WalletManager>>> {
|
||||
return managers.asSharedFlow()
|
||||
}
|
||||
|
||||
suspend fun update(
|
||||
f: suspend (HashMap<UserWalletId, List<WalletManager>>) -> HashMap<UserWalletId, List<WalletManager>>,
|
||||
) {
|
||||
while (mutex.isLocked) {
|
||||
delay(timeMillis = 60)
|
||||
}
|
||||
|
||||
mutex.withLock {
|
||||
val prevState = managers.first()
|
||||
managers.emit(f(prevState))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.tangem.tap.domain.walletStores.storage
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal object WalletStoresStorage {
|
||||
private val stores = MutableSharedFlow<HashMap<UserWalletId, List<WalletStoreModel>>>(replay = 1)
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
stores.tryEmit(hashMapOf())
|
||||
}
|
||||
|
||||
fun getAll(): SharedFlow<Map<UserWalletId, List<WalletStoreModel>>> {
|
||||
return stores.asSharedFlow()
|
||||
}
|
||||
|
||||
suspend fun update(
|
||||
f: suspend (HashMap<UserWalletId, List<WalletStoreModel>>) -> HashMap<UserWalletId, List<WalletStoreModel>>,
|
||||
) {
|
||||
while (mutex.isLocked) {
|
||||
delay(timeMillis = 60)
|
||||
}
|
||||
|
||||
mutex.withLock {
|
||||
val prevState = stores.first()
|
||||
stores.emit(f(prevState))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -386,7 +386,6 @@ class DetailsMiddleware {
|
|||
|
||||
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
|
||||
return userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
|
||||
deleteSavedAccessCodes()
|
||||
|
|
|
|||
|
|
@ -158,7 +158,6 @@ internal class WelcomeMiddleware {
|
|||
|
||||
private suspend fun disableUserWalletsSaving() {
|
||||
userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.flatMap { tangemSdkManager.clearSavedUserCodes() }
|
||||
.doOnFailure { e ->
|
||||
Timber.e(e, "Unable to clear user wallets")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -45,7 +44,6 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavControl
|
|||
var userTokensRepository: UserTokensRepository? = null
|
||||
var mainStore: Store<AppState>? = null
|
||||
var tangemSdkManager: TangemSdkManager? = null
|
||||
var walletStoresManager: WalletStoresManager? = null
|
||||
var appFiatCurrency: FiatCurrency = FiatCurrency.Default
|
||||
var exchangeService: ExchangeService? = null
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue