Updated on 2026-08-14
This commit is contained in:
parent
d3e3f5cfd5
commit
a0f1a6591c
28 changed files with 905 additions and 44 deletions
|
|
@ -37,6 +37,10 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
|
|||
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.di.provideDummyImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.persistence.CardBalanceStateAdapter
|
||||
|
|
@ -60,6 +64,14 @@ lateinit var shopService: TangemShopService
|
|||
lateinit var assetReader: AssetReader
|
||||
lateinit var userTokensRepository: UserTokensRepository
|
||||
|
||||
val walletStoresManager by lazy {
|
||||
WalletStoresManager.provideDummyImplementation()
|
||||
}
|
||||
|
||||
val totalFiatBalanceCalculator by lazy {
|
||||
TotalFiatBalanceCalculator.provideDefaultImplementation()
|
||||
}
|
||||
|
||||
@HiltAndroidApp
|
||||
class TapApplication : Application(), ImageLoaderFactory {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.tap.features.shop.redux.ShopReducer
|
|||
import com.tangem.tap.features.tokens.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
shopState = ShopReducer.reduce(action, state.shopState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
||||
walletSelectorState = WalletSelectorReducer.reduce(action, state),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware
|
|||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
|
|
@ -88,6 +89,7 @@ data class AppState(
|
|||
ShopMiddleware().shopMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
SaveWalletMiddleware().middleware,
|
||||
WalletSelectorMiddleware().middleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.tap.domain.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Represents fiat balance of [WalletStoreModel] list
|
||||
* @property amount Amount of the total balance
|
||||
* */
|
||||
sealed class TotalFiatBalance {
|
||||
open val amount: BigDecimal = BigDecimal.ZERO
|
||||
|
||||
object Loading : TotalFiatBalance()
|
||||
|
||||
class Refreshing(
|
||||
override val amount: BigDecimal,
|
||||
) : TotalFiatBalance()
|
||||
|
||||
class Error(
|
||||
override val amount: BigDecimal,
|
||||
) : TotalFiatBalance()
|
||||
|
||||
class Loaded(
|
||||
override val amount: BigDecimal,
|
||||
) : TotalFiatBalance()
|
||||
}
|
||||
|
|
@ -2,9 +2,17 @@ package com.tangem.tap.domain.model
|
|||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
|
||||
/**
|
||||
* Represents user's wallet which stored in app persistence
|
||||
* @param name User's wallet name
|
||||
* @param walletId User's wallet [UserWalletId]
|
||||
* @param artworkUrl User wallet card artwork URL
|
||||
* @param cardsInWallet List of cards IDs assigned with this user's wallet
|
||||
* @param scanResponse [ScanResponse] of primary user's wallet card.
|
||||
* TODO: Replace with [com.tangem.domain.common.CardDTO]
|
||||
* @property cardId ID of user's wallet primary card
|
||||
* */
|
||||
data class UserWallet(
|
||||
val name: String,
|
||||
val walletId: UserWalletId,
|
||||
|
|
@ -14,24 +22,4 @@ data class UserWallet(
|
|||
) {
|
||||
val cardId: String
|
||||
get() = scanResponse.card.cardId
|
||||
|
||||
val hasAccessCode: Boolean
|
||||
get() = scanResponse.card.isAccessCodeSet
|
||||
|
||||
companion object {
|
||||
suspend operator fun invoke(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: Set<String>? = null,
|
||||
): UserWallet {
|
||||
return with(scanResponse) {
|
||||
UserWallet(
|
||||
walletId = card.userWalletId,
|
||||
name = productType.name,
|
||||
artworkUrl = card.getOrLoadCardArtworkUrl(),
|
||||
cardsInWallet = backupCardsIds?.plus(card.cardId) ?: setOf(card.cardId),
|
||||
scanResponse = this,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.tap.domain.model
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Contains info about wallet store currency
|
||||
* @param currency Wallet's [Currency]
|
||||
* @param status Wallet's [Status], represents current status of that currency
|
||||
* @param walletAddresses List of wallet data [AddressData]
|
||||
* @param existentialDeposit Amount that must be held on currency's balance, if balance is below that amount all
|
||||
* founds will be destroyed. Null if currency don't have existential deposit
|
||||
* @param fiatRate Wallet's fiat rate, used to calculate fiat balance. Null if not provided
|
||||
* */
|
||||
data class WalletDataModel(
|
||||
val currency: Currency,
|
||||
val status: Status,
|
||||
val walletAddresses: List<AddressData>,
|
||||
val existentialDeposit: BigDecimal?,
|
||||
val fiatRate: BigDecimal?,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represent current status of currency
|
||||
* @property amount Currency amount
|
||||
* @property pendingTransactions List of currency [PendingTransaction] sent in currency's blockchain
|
||||
* @property errorMessage Status error message, null if not provided
|
||||
* @property isErrorStatus true if current status is error status, false otherwise
|
||||
* */
|
||||
sealed class Status {
|
||||
open val amount: BigDecimal = BigDecimal.ZERO
|
||||
open val pendingTransactions: List<PendingTransaction> = emptyList()
|
||||
open val errorMessage: String? = null
|
||||
open val isErrorStatus: Boolean = false
|
||||
}
|
||||
|
||||
object Loading : Status()
|
||||
|
||||
data class VerifiedOnline(
|
||||
override val amount: BigDecimal,
|
||||
) : Status()
|
||||
|
||||
data class TransactionInProgress(
|
||||
override val amount: BigDecimal,
|
||||
override val pendingTransactions: List<PendingTransaction>,
|
||||
) : Status()
|
||||
|
||||
data class SameCurrencyTransactionInProgress(
|
||||
override val amount: BigDecimal,
|
||||
override val pendingTransactions: List<PendingTransaction>,
|
||||
) : Status()
|
||||
|
||||
data class NoAccount(
|
||||
val amountToCreateAccount: BigDecimal?,
|
||||
) : Status() {
|
||||
override val isErrorStatus: Boolean = true
|
||||
}
|
||||
|
||||
data class Unreachable(
|
||||
override val errorMessage: String?,
|
||||
) : Status() {
|
||||
override val isErrorStatus: Boolean = true
|
||||
}
|
||||
|
||||
object MissedDerivation : Status() {
|
||||
override val isErrorStatus: Boolean = true
|
||||
}
|
||||
|
||||
data class Refreshing(
|
||||
override val amount: BigDecimal,
|
||||
override val pendingTransactions: List<PendingTransaction>,
|
||||
override val errorMessage: String?,
|
||||
) : Status() {
|
||||
override val isErrorStatus: Boolean = errorMessage != null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.tap.domain.model
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.WalletStoreModel.WalletRent
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Contains info about the blockchain and its currencies
|
||||
* @param userWalletId ID of the [UserWallet] which uses that store
|
||||
* @param blockchainNetwork Store's [BlockchainNetwork]
|
||||
* @param walletManager Store's [WalletManager], may be null if it fails to create this manager
|
||||
* @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies
|
||||
* @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then
|
||||
* [WalletRent.exemptionAmount]
|
||||
* */
|
||||
data class WalletStoreModel(
|
||||
val userWalletId: UserWalletId,
|
||||
val blockchainNetwork: BlockchainNetwork,
|
||||
val walletManager: WalletManager?,
|
||||
val walletsData: List<WalletDataModel>,
|
||||
val walletRent: WalletRent?,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents wallet blockchain rent
|
||||
* @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than
|
||||
* the [WalletRent.exemptionAmount]
|
||||
* @param exemptionAmount Amount that should be on the blockchain balance not to pay rent
|
||||
* */
|
||||
data class WalletRent(
|
||||
val rent: BigDecimal,
|
||||
val exemptionAmount: BigDecimal,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.model.builders
|
||||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
|
||||
class UserWalletBuilder(
|
||||
private val scanResponse: ScanResponse,
|
||||
) {
|
||||
private var backupCardsIds: Set<String> = emptySet()
|
||||
|
||||
fun setBackupCardsIds(backupCardsIds: Set<String>?) = this.apply {
|
||||
if (backupCardsIds != null) {
|
||||
this.backupCardsIds = backupCardsIds
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun build(): UserWallet {
|
||||
return with(scanResponse) {
|
||||
UserWallet(
|
||||
walletId = card.userWalletId,
|
||||
name = productType.name,
|
||||
artworkUrl = card.getOrLoadCardArtworkUrl(),
|
||||
cardsInWallet = backupCardsIds.plus(card.cardId),
|
||||
scanResponse = this,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.tap.domain.totalBalance
|
||||
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface TotalFiatBalanceCalculator {
|
||||
suspend fun calculate(
|
||||
prevAmount: BigDecimal,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): TotalFiatBalance
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.domain.totalBalance.di
|
||||
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import com.tangem.tap.domain.totalBalance.implementation.DefaultTotalFiatBalanceCalculator
|
||||
|
||||
fun TotalFiatBalanceCalculator.Companion.provideDefaultImplementation(): TotalFiatBalanceCalculator {
|
||||
return DefaultTotalFiatBalanceCalculator()
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.tap.domain.totalBalance.implementation
|
||||
|
||||
import com.tangem.tap.common.extensions.toFiatValue
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
||||
override suspend fun calculate(
|
||||
prevAmount: BigDecimal,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): TotalFiatBalance {
|
||||
return if (walletStores.isEmpty()) {
|
||||
TotalFiatBalance.Loading
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
val walletsData = walletStores
|
||||
.asSequence()
|
||||
.flatMap { it.walletsData }
|
||||
val calculateAmount = { walletsData.calculateTotalFiatAmount() }
|
||||
|
||||
when (walletsData.findStatus()) {
|
||||
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
|
||||
TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(prevAmount)
|
||||
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
|
||||
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<WalletDataModel>.findStatus(): TotalFiatBalanceStatus {
|
||||
return this
|
||||
.mapToStatus()
|
||||
.reduce { prevStatus, newStatus ->
|
||||
getCurrentStatus(prevStatus, newStatus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<WalletDataModel>.mapToStatus(): Sequence<TotalFiatBalanceStatus> {
|
||||
return this.map { walletData ->
|
||||
when (walletData.status) {
|
||||
is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing
|
||||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
is WalletDataModel.NoAccount,
|
||||
-> TotalFiatBalanceStatus.Loaded
|
||||
is WalletDataModel.Unreachable,
|
||||
is WalletDataModel.MissedDerivation,
|
||||
-> TotalFiatBalanceStatus.Error
|
||||
is WalletDataModel.Loading -> TotalFiatBalanceStatus.Loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal {
|
||||
return this
|
||||
.map { walletData ->
|
||||
walletData.fiatRate
|
||||
?.let { walletData.status.amount.toFiatValue(it) }
|
||||
?: BigDecimal.ZERO
|
||||
}
|
||||
.reduce(BigDecimal::plus)
|
||||
}
|
||||
|
||||
private fun getCurrentStatus(
|
||||
prevStatus: TotalFiatBalanceStatus,
|
||||
newStatus: TotalFiatBalanceStatus,
|
||||
): TotalFiatBalanceStatus {
|
||||
return when (prevStatus) {
|
||||
TotalFiatBalanceStatus.Loading -> prevStatus
|
||||
TotalFiatBalanceStatus.Refreshing -> when (newStatus) {
|
||||
TotalFiatBalanceStatus.Loading -> prevStatus
|
||||
TotalFiatBalanceStatus.Refreshing,
|
||||
TotalFiatBalanceStatus.Error,
|
||||
TotalFiatBalanceStatus.Loaded,
|
||||
-> newStatus
|
||||
}
|
||||
TotalFiatBalanceStatus.Loaded,
|
||||
TotalFiatBalanceStatus.Error,
|
||||
-> when (newStatus) {
|
||||
TotalFiatBalanceStatus.Loading,
|
||||
TotalFiatBalanceStatus.Refreshing,
|
||||
TotalFiatBalanceStatus.Error,
|
||||
-> newStatus
|
||||
TotalFiatBalanceStatus.Loaded -> prevStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TotalFiatBalanceStatus {
|
||||
Loading,
|
||||
Refreshing,
|
||||
Error,
|
||||
Loaded,
|
||||
}
|
||||
}
|
||||
|
|
@ -25,5 +25,7 @@ interface UserWalletsListManager {
|
|||
suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
suspend fun get(walletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -55,4 +55,10 @@ class DummyUserWalletsListManager : UserWalletsListManager {
|
|||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun get(walletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return catching {
|
||||
error("Not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.domain.walletStores
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletStoresManager {
|
||||
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
|
||||
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
|
||||
|
||||
suspend fun delete(userWalletsIds: List<String>): CompletionResult<Unit>
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
suspend fun fetch(
|
||||
userWallet: UserWallet,
|
||||
refresh: Boolean = false,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
suspend fun fetch(
|
||||
userWallets: List<UserWallet>,
|
||||
refresh: Boolean = false,
|
||||
): CompletionResult<Unit>
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.domain.walletStores.di
|
||||
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.domain.walletStores.implementation.DummyWalletStoresManager
|
||||
|
||||
fun WalletStoresManager.Companion.provideDummyImplementation(): WalletStoresManager {
|
||||
return DummyWalletStoresManager()
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.tap.domain.walletStores.implementation
|
||||
|
||||
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 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 delete(userWalletsIds: List<String>): 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,12 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.map
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -65,10 +65,9 @@ internal class SaveWalletMiddleware {
|
|||
?: return
|
||||
|
||||
scope.launch {
|
||||
val userWallet = UserWallet(
|
||||
scanResponse = scanResponse,
|
||||
backupCardsIds = state.additionalInfo?.backupCardsIds,
|
||||
)
|
||||
val userWallet = UserWalletBuilder(scanResponse)
|
||||
.setBackupCardsIds(backupCardsIds = state.additionalInfo?.backupCardsIds)
|
||||
.build()
|
||||
|
||||
userWalletsListManager.save(userWallet)
|
||||
.flatMap {
|
||||
|
|
@ -108,14 +107,6 @@ internal class SaveWalletMiddleware {
|
|||
cardsIds = userWallet.cardsInWallet,
|
||||
)
|
||||
}
|
||||
userWallet.hasAccessCode -> {
|
||||
tangemSdkManager
|
||||
.scanCard(
|
||||
cardId = userWallet.cardId,
|
||||
useBiometricsForAccessCode = true,
|
||||
)
|
||||
.map { /* no-op */ }
|
||||
}
|
||||
else -> {
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
|
||||
data class UserWalletModel(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val artworkUrl: String,
|
||||
val type: Type,
|
||||
val fiatBalance: TotalFiatBalance,
|
||||
) {
|
||||
sealed interface Type {
|
||||
data class SingleCurrency(
|
||||
val blockchainName: String?,
|
||||
) : Type
|
||||
|
||||
data class MultiCurrency(
|
||||
val tokensCount: Int,
|
||||
val cardsInWallet: Int,
|
||||
) : Type
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,33 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal sealed interface WalletSelectorAction : Action {
|
||||
data class UserWalletsLoaded(
|
||||
val userWallets: List<UserWallet>,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class SelectedWalletChanged(
|
||||
val selectedWallet: UserWallet,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class IsLockedChanged(
|
||||
val isLocked: Boolean,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class WalletStoresChanged(
|
||||
val walletsStores: Map<UserWalletId, List<WalletStoreModel>>,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class BalanceLoaded(
|
||||
val userWalletModel: UserWalletModel,
|
||||
) : WalletSelectorAction
|
||||
|
||||
object UnlockWithBiometry : WalletSelectorAction {
|
||||
object Success : WalletSelectorAction
|
||||
data class Error(val error: TangemError) : WalletSelectorAction
|
||||
|
|
@ -27,5 +51,10 @@ internal sealed interface WalletSelectorAction : Action {
|
|||
data class Error(val error: TangemError) : WalletSelectorAction
|
||||
}
|
||||
|
||||
data class ChangeAppCurrency(
|
||||
val fiatCurrency: FiatCurrency,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class HandleError(val error: TangemError) : WalletSelectorAction
|
||||
object CloseError : WalletSelectorAction
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.map
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
internal class WalletSelectorMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, appStateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
val appState = appStateProvider()
|
||||
if (action is WalletSelectorAction && appState != null) {
|
||||
handleAction(action, appState.walletSelectorState)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: WalletSelectorAction, state: WalletSelectorState) {
|
||||
when (action) {
|
||||
is WalletSelectorAction.UserWalletsLoaded -> {
|
||||
fetchWalletStores(action.userWallets)
|
||||
}
|
||||
is WalletSelectorAction.WalletStoresChanged -> {
|
||||
updateBalances(action.walletsStores, state)
|
||||
}
|
||||
is WalletSelectorAction.UnlockWithBiometry -> {
|
||||
unlockWalletsWithBiometry()
|
||||
}
|
||||
is WalletSelectorAction.AddWallet -> {
|
||||
addWallet()
|
||||
}
|
||||
is WalletSelectorAction.SelectWallet -> {
|
||||
selectWallet(action.walletId)
|
||||
}
|
||||
is WalletSelectorAction.RemoveWallets -> {
|
||||
removeWallets(action.walletIdsToRemove, state)
|
||||
}
|
||||
is WalletSelectorAction.RenameWallet -> {
|
||||
renameWallet(action.walletId, action.newName)
|
||||
}
|
||||
is WalletSelectorAction.ChangeAppCurrency,
|
||||
is WalletSelectorAction.AddWallet.Success,
|
||||
is WalletSelectorAction.AddWallet.Error,
|
||||
is WalletSelectorAction.SelectedWalletChanged,
|
||||
is WalletSelectorAction.UnlockWithBiometry.Error,
|
||||
is WalletSelectorAction.UnlockWithBiometry.Success,
|
||||
is WalletSelectorAction.BalanceLoaded,
|
||||
is WalletSelectorAction.IsLockedChanged,
|
||||
is WalletSelectorAction.HandleError,
|
||||
is WalletSelectorAction.CloseError,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchWalletStores(userWallets: List<UserWallet>) {
|
||||
scope.launch {
|
||||
walletStoresManager.fetch(userWallets)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to fetch wallet stores")
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBalances(walletStores: Map<UserWalletId, List<WalletStoreModel>>, state: WalletSelectorState) {
|
||||
walletStores.forEach { (walletId, walletStores) ->
|
||||
scope.launch {
|
||||
val updatedWallet = state.wallets
|
||||
.find { it.id == walletId.stringValue }
|
||||
?.let { wallet ->
|
||||
wallet.copy(
|
||||
type = when (val type = wallet.type) {
|
||||
is UserWalletModel.Type.MultiCurrency -> type.copy(
|
||||
tokensCount = walletStores.flatMap { it.walletsData }.size,
|
||||
)
|
||||
is UserWalletModel.Type.SingleCurrency -> type.copy(
|
||||
blockchainName = walletStores
|
||||
.firstOrNull()
|
||||
?.blockchainNetwork
|
||||
?.blockchain
|
||||
?.fullName,
|
||||
)
|
||||
},
|
||||
fiatBalance = totalFiatBalanceCalculator.calculate(
|
||||
prevAmount = wallet.fiatBalance.amount,
|
||||
walletStores = walletStores,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (updatedWallet != null) {
|
||||
store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unlockWalletsWithBiometry() {
|
||||
scope.launch {
|
||||
userWalletsListManager.unlockWithBiometry()
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Success)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWallet() = scope.launch {
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
userWalletsListManager.save(userWallet)
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
val selectedWallet = userWalletsListManager.selectedUserWallet.first()
|
||||
val isSavedWalletSelected = userWallet == selectedWallet
|
||||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
|
||||
if (isSavedWalletSelected) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectWallet(id: String) {
|
||||
scope.launch {
|
||||
userWalletsListManager.selectWallet(UserWalletId(id))
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
.doOnSuccess { selectedWallet ->
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.onUserWalletSelected(selectedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeWallets(walletIdsToRemove: List<String>, state: WalletSelectorState) {
|
||||
scope.launch {
|
||||
when (walletIdsToRemove.size) {
|
||||
state.wallets.size -> { // Removing all wallets
|
||||
userWalletsListManager.clear()
|
||||
.flatMap { walletStoresManager.clear() }
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) })
|
||||
.flatMap { walletStoresManager.delete(walletIdsToRemove) }
|
||||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renameWallet(walletId: String, newName: String) {
|
||||
scope.launch {
|
||||
userWalletsListManager.get(walletId = UserWalletId(walletId))
|
||||
.map { it.copy(name = newName) }
|
||||
.flatMap { userWalletsListManager.update(it) }
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.HandleError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(
|
||||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
ScanCardProcessor.scan(
|
||||
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
|
||||
onSuccess = {
|
||||
onCardScanned(it)
|
||||
},
|
||||
onFailure = { error ->
|
||||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error))
|
||||
},
|
||||
onWalletNotCreated = {
|
||||
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.extensions.replaceByOrAdd
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal object WalletSelectorReducer {
|
||||
fun reduce(action: Action, state: AppState): WalletSelectorState {
|
||||
return if (action is WalletSelectorAction) {
|
||||
internalReduce(action, state.walletSelectorState)
|
||||
} else state.walletSelectorState
|
||||
}
|
||||
|
||||
private fun internalReduce(action: WalletSelectorAction, state: WalletSelectorState): WalletSelectorState {
|
||||
return when (action) {
|
||||
is WalletSelectorAction.UserWalletsLoaded -> state.copy(
|
||||
wallets = action.userWallets.updateWalletsModels(state.wallets),
|
||||
)
|
||||
is WalletSelectorAction.SelectedWalletChanged -> state.copy(
|
||||
selectedWalletId = action.selectedWallet.walletId.stringValue,
|
||||
)
|
||||
is WalletSelectorAction.IsLockedChanged -> state.copy(
|
||||
isLocked = action.isLocked,
|
||||
)
|
||||
is WalletSelectorAction.BalanceLoaded -> state.copy(
|
||||
wallets = state.wallets.updateWithBalance(action.userWalletModel),
|
||||
)
|
||||
is WalletSelectorAction.HandleError -> state.copy(error = action.error)
|
||||
is WalletSelectorAction.CloseError -> state.copy(error = null)
|
||||
is WalletSelectorAction.UnlockWithBiometry -> state.copy(
|
||||
isUnlockInProgress = true,
|
||||
)
|
||||
is WalletSelectorAction.UnlockWithBiometry.Error -> state.copy(
|
||||
isUnlockInProgress = false,
|
||||
error = action.error,
|
||||
)
|
||||
is WalletSelectorAction.UnlockWithBiometry.Success -> state.copy(
|
||||
isUnlockInProgress = false,
|
||||
)
|
||||
is WalletSelectorAction.AddWallet -> state.copy(
|
||||
isCardSavingInProgress = true,
|
||||
)
|
||||
is WalletSelectorAction.AddWallet.Error -> state.copy(
|
||||
isCardSavingInProgress = false,
|
||||
error = action.error,
|
||||
)
|
||||
is WalletSelectorAction.AddWallet.Success -> state.copy(
|
||||
isCardSavingInProgress = false,
|
||||
)
|
||||
is WalletSelectorAction.ChangeAppCurrency -> state.copy(
|
||||
fiatCurrency = action.fiatCurrency,
|
||||
)
|
||||
is WalletSelectorAction.WalletStoresChanged,
|
||||
is WalletSelectorAction.SelectWallet,
|
||||
is WalletSelectorAction.RemoveWallets,
|
||||
is WalletSelectorAction.RenameWallet,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UserWallet>.updateWalletsModels(prevWallets: List<UserWalletModel>): List<UserWalletModel> {
|
||||
return this.map { userWallet ->
|
||||
prevWallets
|
||||
.find { it.id == userWallet.walletId.stringValue }
|
||||
?.copy(
|
||||
name = userWallet.name,
|
||||
artworkUrl = userWallet.artworkUrl,
|
||||
)
|
||||
?: with(userWallet) {
|
||||
UserWalletModel(
|
||||
id = walletId.stringValue,
|
||||
name = name,
|
||||
artworkUrl = artworkUrl,
|
||||
type = getType(),
|
||||
fiatBalance = TotalFiatBalance.Loading,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UserWalletModel>.updateWithBalance(
|
||||
userWalletModel: UserWalletModel,
|
||||
): List<UserWalletModel> {
|
||||
return ArrayList(this).apply {
|
||||
replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id }
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.getType(): UserWalletModel.Type {
|
||||
return if (scanResponse.card.isMultiwalletAllowed) {
|
||||
UserWalletModel.Type.MultiCurrency(
|
||||
tokensCount = 0,
|
||||
cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active)
|
||||
?.cardCount?.inc()
|
||||
?: 1,
|
||||
)
|
||||
} else {
|
||||
UserWalletModel.Type.SingleCurrency(
|
||||
blockchainName = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,15 @@
|
|||
package com.tangem.tap.features.walletSelector.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import org.rekotlin.StateType
|
||||
|
||||
class WalletSelectorState : StateType
|
||||
data class WalletSelectorState(
|
||||
val wallets: List<UserWalletModel> = emptyList(),
|
||||
val selectedWalletId: String? = null,
|
||||
val isLocked: Boolean = false,
|
||||
val fiatCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val isCardSavingInProgress: Boolean = false,
|
||||
val isUnlockInProgress: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
) : StateType
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.tap.features.walletSelector.ui
|
||||
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.walletSelector.redux.UserWalletModel
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
|
||||
|
||||
internal fun WalletSelectorScreenState.updateWithNewState(
|
||||
newState: WalletSelectorState,
|
||||
): WalletSelectorScreenState {
|
||||
val walletsUi = newState.wallets.toUiModels(newState.fiatCurrency)
|
||||
val walletsIds = walletsUi.map { it.id }
|
||||
val multiCurrencyWallets = arrayListOf<MultiCurrencyUserWalletItem>()
|
||||
val singleCurrencyWallets = arrayListOf<SingleCurrencyUserWalletItem>()
|
||||
walletsUi.forEach { wallet ->
|
||||
when (wallet) {
|
||||
is MultiCurrencyUserWalletItem -> {
|
||||
multiCurrencyWallets.add(wallet)
|
||||
}
|
||||
is SingleCurrencyUserWalletItem -> {
|
||||
singleCurrencyWallets.add(wallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.copy(
|
||||
multiCurrencyWallets = multiCurrencyWallets,
|
||||
singleCurrencyWallets = singleCurrencyWallets,
|
||||
selectedWalletId = newState.selectedWalletId,
|
||||
editingWalletsIds = editingWalletsIds.filter { it in walletsIds },
|
||||
isLocked = newState.isLocked,
|
||||
showUnlockProgress = newState.isUnlockInProgress,
|
||||
showAddCardProgress = newState.isCardSavingInProgress,
|
||||
error = newState.error
|
||||
?.takeUnless { it.silent }
|
||||
?.let { error ->
|
||||
error.messageResId?.let { TextReference.Res(it) }
|
||||
?: TextReference.Str(error.customMessage)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<UserWalletModel>.toUiModels(
|
||||
appCurrency: FiatCurrency,
|
||||
): Sequence<UserWalletItem> {
|
||||
return this.asSequence().map { userWalletModel ->
|
||||
with(userWalletModel) {
|
||||
val balance = UserWalletItem.Balance(
|
||||
amount = fiatBalance.amount.toFormattedFiatValue(appCurrency.symbol),
|
||||
isLoading = fiatBalance is TotalFiatBalance.Loading,
|
||||
)
|
||||
when (type) {
|
||||
is UserWalletModel.Type.MultiCurrency -> MultiCurrencyUserWalletItem(
|
||||
id = id,
|
||||
name = name,
|
||||
imageUrl = artworkUrl,
|
||||
balance = balance,
|
||||
cardsInWallet = type.cardsInWallet,
|
||||
tokensCount = type.tokensCount,
|
||||
)
|
||||
is UserWalletModel.Type.SingleCurrency -> SingleCurrencyUserWalletItem(
|
||||
id = id,
|
||||
name = name,
|
||||
imageUrl = artworkUrl,
|
||||
balance = balance,
|
||||
tokenName = type.blockchainName ?: "—",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
|||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
|
||||
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
|
||||
|
|
@ -33,7 +34,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
state: WalletSelectorScreenState,
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage)
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val renameWalletDialog by rememberUpdatedState(newValue = state.renameWalletDialog)
|
||||
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.walletSelector.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
|
||||
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
|
||||
|
|
@ -16,5 +16,5 @@ internal data class WalletSelectorScreenState(
|
|||
val renameWalletDialog: RenameWalletDialog? = null,
|
||||
val showAddCardProgress: Boolean = false,
|
||||
val showUnlockProgress: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
val error: TextReference? = null,
|
||||
)
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
package com.tangem.tap.features.walletSelector.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
|
|
@ -18,6 +24,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
subscribeToStoreChanges()
|
||||
bootstrapWalletListChanges()
|
||||
bootstrapWalletsStoresChanges()
|
||||
bootstrapAppFiatCurrency()
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
|
|
@ -104,7 +111,9 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
}
|
||||
|
||||
override fun newState(state: WalletSelectorState) {
|
||||
// TODO: Update UI state
|
||||
stateInternal.update { prevState ->
|
||||
prevState.updateWithNewState(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
@ -135,10 +144,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
}
|
||||
|
||||
private fun bootstrapWalletListChanges() {
|
||||
// TODO
|
||||
userWalletsListManager.userWallets
|
||||
.onEach {
|
||||
store.dispatchOnMain(WalletSelectorAction.UserWalletsLoaded(userWallets = it))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
userWalletsListManager.selectedUserWallet
|
||||
.onEach {
|
||||
store.dispatchOnMain(WalletSelectorAction.SelectedWalletChanged(selectedWallet = it))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
userWalletsListManager.isLocked
|
||||
.onEach {
|
||||
store.dispatchOnMain(WalletSelectorAction.IsLockedChanged(isLocked = it))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun bootstrapWalletsStoresChanges() {
|
||||
// TODO
|
||||
walletStoresManager.getAll()
|
||||
.onEach { walletStores ->
|
||||
store.dispatchOnMain(WalletSelectorAction.WalletStoresChanged(walletStores))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun bootstrapAppFiatCurrency() {
|
||||
store.dispatch(
|
||||
WalletSelectorAction.ChangeAppCurrency(
|
||||
fiatCurrency = store.state.globalState.appCurrency,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.builders.UserWalletBuilder
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -58,7 +58,7 @@ internal class WelcomeMiddleware {
|
|||
|
||||
private fun proceedWithCard() = scope.launch {
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWallet = UserWallet(scanResponse)
|
||||
val userWallet = UserWalletBuilder(scanResponse).build()
|
||||
|
||||
userWalletsListManager.unlockWithCard(userWallet)
|
||||
.doOnFailure { error ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue