Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-11 11:42:09 +03:00
parent 770fad5955
commit e1aee494ef
14 changed files with 174 additions and 36 deletions

View file

@ -1,10 +1,11 @@
package com.tangem.tap.domain.model
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tap.domain.model.WalletDataModel.AddressData
import com.tangem.tap.domain.model.WalletDataModel.Status
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.AddressData
import java.math.BigDecimal
/**
@ -21,7 +22,8 @@ import java.math.BigDecimal
data class WalletDataModel(
val currency: Currency,
val status: Status,
val walletAddresses: List<AddressData>,
// FIXME: Left only selected wallet address here and move list of wallet addresses to WalletStoreModel
val walletAddresses: WalletAddresses?,
val existentialDeposit: BigDecimal?,
val fiatRate: BigDecimal?,
val isCardSingleToken: Boolean,
@ -29,6 +31,18 @@ data class WalletDataModel(
val historyTransactions: List<TransactionData>?,
) {
data class WalletAddresses(
val selectedAddress: AddressData,
val list: List<AddressData>,
)
data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
val exploreUrl: String,
)
/**
* Represent current status of currency
* @property amount Currency amount

View file

@ -9,6 +9,7 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import java.math.BigDecimal
// FIXME: Move list of wallet addresses from WalletDataModel to this class
/**
* Contains info about the blockchain and its currencies
*

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.TapWorkarounds.derivationStyle
@ -107,7 +108,7 @@ private fun BlockchainNetwork.getBlockchainWalletData(
return WalletDataModel(
currency = currency,
status = WalletDataModel.Loading,
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
walletAddresses = walletManager?.wallet?.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
@ -131,7 +132,7 @@ private fun BlockchainNetwork.getTokensWalletsData(
WalletDataModel(
currency = currency,
status = WalletDataModel.Loading,
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
walletAddresses = walletManager?.wallet?.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = token == primaryToken,
@ -149,7 +150,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal
derivationPath = wallet.publicKey.derivationPath?.rawPath,
),
status = WalletDataModel.Loading,
walletAddresses = wallet.createAddressesData(),
walletAddresses = wallet.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
@ -167,7 +168,7 @@ private fun Token.toTokenWalletData(walletManager: WalletManager, primaryToken:
derivationPath = wallet.publicKey.derivationPath?.rawPath,
),
status = WalletDataModel.Loading,
walletAddresses = wallet.createAddressesData(),
walletAddresses = wallet.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = this == primaryToken,
@ -178,4 +179,19 @@ private fun Token.toTokenWalletData(walletManager: WalletManager, primaryToken:
private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? {
return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()
}
private fun Wallet.getWalletAddresses(): WalletDataModel.WalletAddresses? {
return this.createAddressesData()
.takeIf { it.isNotEmpty() }
?.map {
// TODO: Will be removed in next MR
with(it) { WalletDataModel.AddressData(address, type, shareUrl, exploreUrl) }
}
?.let { addresses ->
WalletDataModel.WalletAddresses(
list = addresses,
selectedAddress = addresses.first(),
)
}
}

View file

@ -11,34 +11,29 @@ sealed class WalletStoresError(code: Int) : TangemError(code) {
override val message: String?
get() = customMessage
@Suppress("MagicNumber")
class FetchFiatRatesError(
currencies: List<String>,
override val cause: Throwable?,
) : WalletStoresError(60011) {
) : WalletStoresError(code = 60011) {
override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies"
}
@Suppress("MagicNumber")
class UnknownBlockchain : WalletStoresError(60012) {
class UnknownBlockchain : WalletStoresError(code = 60012) {
override var customMessage: String = "Unknown blockchain"
}
@Suppress("MagicNumber")
object NoInternetConnection : WalletStoresError(60013) {
object NoInternetConnection : WalletStoresError(code = 60013) {
override var customMessage: String = "No internet connection"
}
@Suppress("MagicNumber")
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) {
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(code = 60014) {
override var customMessage: String = "Wallet manager can not be created for $blockchain"
}
@Suppress("MagicNumber")
class UpdateWalletManagerTokensError(
blockchain: Blockchain,
override val cause: Throwable,
) : WalletStoresError(600015) {
) : WalletStoresError(code = 600015) {
override var customMessage: String = "Unable to update wallet manager tokens for currency $blockchain: $cause"
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.tap.domain.walletStores
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
interface WalletStoresManager {
@ -22,6 +24,7 @@ interface WalletStoresManager {
* @return [Flow] with [WalletStoreModel] list
* */
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
/**
@ -71,6 +74,12 @@ interface WalletStoresManager {
* */
suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit>
suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit>
// For provider
companion object
}

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
@ -18,6 +19,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
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
@ -103,6 +105,21 @@ internal class DefaultWalletStoresManager(
}
}
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)

View file

@ -1,10 +1,12 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.UserWallet
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
@ -40,4 +42,12 @@ internal class DummyWalletStoresManager : WalletStoresManager {
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)
}
}

View file

@ -24,5 +24,19 @@ interface WalletStoresRepository {
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
}

View file

@ -69,6 +69,25 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository {
}
}
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,

View file

@ -3,6 +3,7 @@ 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.tap.common.extensions.getBlockchainTxHistory
import com.tangem.tap.common.extensions.getTokenTxHistory
@ -155,7 +156,6 @@ internal fun WalletDataModel.updateWithSelf(newWalletData: WalletDataModel): Wal
is WalletDataModel.VerifiedOnline,
-> newStatus
},
walletAddresses = newWalletData.walletAddresses,
existentialDeposit = newWalletData.existentialDeposit,
fiatRate = newWalletData.fiatRate ?: oldWalletData.fiatRate,
)
@ -193,6 +193,31 @@ internal fun List<WalletDataModel>.updateWithSelf(newWalletsData: List<WalletDat
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 addresses = oldWalletData.walletAddresses
?: return this
val selectedAddress = addresses.list
.firstOrNull { it.type == addressType }
?: addresses.selectedAddress
val updatedWalletData = oldWalletData.copy(
walletAddresses = addresses.copy(
selectedAddress = selectedAddress,
),
)
if (oldWalletData == updatedWalletData) return this
return this.toMutableList().apply {
this[index] = updatedWalletData
}
}
internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean {
return this.currency == other.currency
}

View file

@ -1,9 +1,11 @@
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.common.util.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(
@ -91,6 +93,12 @@ internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?)
)
}
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,

View file

@ -11,6 +11,7 @@ import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAddresses
import com.tangem.tap.features.wallet.redux.WalletData
@ -76,10 +77,15 @@ private fun WalletDataModel.mapToReduxModel(
return WalletData(
currency = currency,
walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress ->
// TODO: Will be updated in next MR
walletAddresses = walletAddresses?.let { addresses ->
WalletAddresses(
selectedAddress = selectedAddress,
list = walletAddresses,
selectedAddress = with(addresses.selectedAddress) {
AddressData(address, type, shareUrl, exploreUrl)
},
list = addresses.list.map {
with(it) { AddressData(address, type, shareUrl, exploreUrl) }
},
)
},
existentialDepositString = existentialDeposit?.toPlainString(),

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
@ -42,6 +43,7 @@ import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.totalFiatBalanceCalculator
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletStoresManager
import com.tangem.utils.coroutines.ifActive
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
@ -204,6 +206,24 @@ class WalletMiddleware {
store.dispatchOnMain(NavigationAction.PopBackTo(screen))
}
is WalletAction.ChangeSelectedAddress -> {
changeSelectedWalletAddress(action.type, walletState)
}
}
}
private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) {
val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard {
Timber.e("Unable to change selected wallet address, no user wallet selected")
return
}
val selectedCurrency = state.selectedCurrency.guard {
Timber.e("Unable to change selected wallet address, no currency selected")
return
}
scope.launch(Dispatchers.Default) {
walletStoresManager.updateSelectedAddress(selectedUserWalletId, selectedCurrency, type)
}
}

View file

@ -13,7 +13,6 @@ import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.redux.ErrorType
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletAddresses
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletStore
@ -102,21 +101,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
)
}
is WalletAction.TradeCryptoAction -> return newState
is WalletAction.ChangeSelectedAddress -> {
val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses
?: return newState
val address = walletAddresses.list.firstOrNull { it.type == action.type }
?: return newState
newState = newState.updateWalletData(
newState.selectedWalletData?.copy(
walletAddresses = WalletAddresses(
selectedAddress = address,
list = walletAddresses.list,
),
),
)
}
is WalletAction.AppCurrencyAction -> {
newState = appCurrencyReducer.reduce(action, newState)
}