Updated on 2026-08-14

This commit is contained in:
Tangem 2022-06-02 12:24:39 +03:00
commit b6d767a488
15 changed files with 174 additions and 86 deletions

View file

@ -1,16 +1,11 @@
package com.tangem.tap.features.wallet.models
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.features.wallet.redux.ProgressState
import java.math.BigDecimal
data class TotalBalance(
val state: State,
val state: ProgressState,
val fiatAmount: BigDecimal,
val fiatCurrency: FiatCurrency,
) {
enum class State {
Loading,
SomeTokensFailed,
Success,
}
}
)

View file

@ -1,7 +1,11 @@
package com.tangem.tap.features.wallet.redux
import android.content.Context
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.card.Card
import com.tangem.tap.common.entities.FiatCurrency
@ -11,8 +15,8 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.wallet.R
import org.rekotlin.Action
import java.math.BigDecimal
import org.rekotlin.Action
sealed class WalletAction : Action {
@ -21,6 +25,7 @@ sealed class WalletAction : Action {
data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction()
object LoadData : WalletAction() {
object Refresh : WalletAction()
data class Failure(val error: TapError) : WalletAction()
}

View file

@ -1,9 +1,15 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
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.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency
@ -19,15 +25,21 @@ import com.tangem.tap.domain.extensions.sellIsAllowed
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.models.hasSendableAmounts
import com.tangem.tap.features.wallet.models.isSendableAmount
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findTotalBalanceState
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
import org.rekotlin.StateType
import kotlin.properties.ReadOnlyProperty
data class WalletState(
@ -191,6 +203,7 @@ data class WalletState(
fun updateWalletStore(walletStore: WalletStore?): WalletState {
return copy(wallets = replaceWalletInWallets(walletStore))
.updateTotalBalance()
.updateProgressState()
}
private fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
@ -208,6 +221,7 @@ data class WalletState(
}
return copy(wallets = updatedWallets + walletStoresMutable)
.updateTotalBalance()
.updateProgressState()
}
fun removeWallet(walletData: WalletData?): WalletState {
@ -270,19 +284,43 @@ data class WalletState(
}
private fun updateTotalBalance(): WalletState {
return if (wallets.isNotEmpty()) {
val walletsData = wallets.flatMap(WalletStore::walletsData)
return if (this.wallets.isNotEmpty()) {
val globalState = store.state.globalState
val walletsData = this.wallets
.flatMap(WalletStore::walletsData)
.filterNot { wallet ->
wallet.currency.isCustomCurrency(
derivationStyle = globalState
.scanResponse
?.card
?.derivationStyle
)
}
this.copy(
totalBalance = TotalBalance(
state = walletsData.findTotalBalanceState(),
state = walletsData.findProgressState(),
fiatAmount = walletsData.calculateTotalFiatAmount(),
fiatCurrency = store.state.globalState.appCurrency
fiatCurrency = globalState.appCurrency
)
)
} else this.copy(
totalBalance = null
)
}
private fun updateProgressState(): WalletState {
return if (this.wallets.isNotEmpty()) {
val walletsData = this.wallets
.flatMap(WalletStore::walletsData)
val newProgressState = walletsData.findProgressState()
this.copy(
state = walletsData.findProgressState(),
error = this.error.takeIf { newProgressState == ProgressState.Error }
)
} else this
}
}
sealed interface WalletDialog : StateDialog {
@ -295,7 +333,7 @@ sealed interface WalletDialog : StateDialog {
) : WalletDialog
}
enum class ProgressState : WidgetState { Loading, Done, Error }
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
enum class ErrorType { NoInternetConnection }

View file

@ -15,7 +15,12 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.shareText
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -28,12 +33,17 @@ import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.getSendableAmounts
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import java.math.BigDecimal
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
@ -41,7 +51,6 @@ import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import timber.log.Timber
import java.math.BigDecimal
class WalletMiddleware {
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
@ -198,7 +207,8 @@ class WalletMiddleware {
store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded)
}
}
is WalletAction.LoadData -> {
is WalletAction.LoadData,
is WalletAction.LoadData.Refresh -> {
scope.launch {
val scanNoteResponse = globalState.scanResponse ?: return@launch
if (walletState.walletsData.isNotEmpty()) {

View file

@ -11,7 +11,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.filterByToken
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
@ -41,8 +40,8 @@ class MultiWalletReducer {
}
val walletData = WalletData(
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
blockchain.blockchain.fullName,
status = BalanceStatus.Loading,
currency = blockchain.blockchain.fullName,
currencySymbol = blockchain.blockchain.currency,
token = cardToken
),
@ -77,8 +76,8 @@ class MultiWalletReducer {
val walletData = WalletData(
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
action.blockchain.blockchain.fullName,
status = BalanceStatus.Loading,
currency = action.blockchain.blockchain.fullName,
currencySymbol = action.blockchain.blockchain.currency,
),
walletAddresses = createAddressList(wallet),
@ -184,7 +183,7 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat
return WalletData(
currencyData = BalanceWidgetData(
BalanceStatus.Loading,
status = BalanceStatus.Loading,
currency = this.name,
currencySymbol = this.symbol
),

View file

@ -12,7 +12,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.filterByToken
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
import com.tangem.tap.features.wallet.models.toPendingTransactions
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.TradeCryptoState
@ -61,7 +60,8 @@ class OnWalletLoadedReducer {
val fiatAmount = walletData.fiatRate?.let { coinAmountValue?.toFiatValue(it) }
val newWalletData = walletData.copy(
currencyData = walletData.currencyData.copy(
status = balanceStatus, currency = wallet.blockchain.fullName,
status = balanceStatus,
currency = wallet.blockchain.fullName,
currencySymbol = wallet.blockchain.currency,
blockchainAmount = coinAmountValue,
amount = coinAmountValue,
@ -110,17 +110,8 @@ class OnWalletLoadedReducer {
val newWallets = tokens + newWalletData
val wallets = walletState.replaceSomeWallets((newWallets))
val state = if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
ProgressState.Loading
} else {
ProgressState.Done
}
return walletState
.updateWalletsData(wallets)
.copy(
state = state,
error = null
)
}
private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {

View file

@ -1,48 +1,59 @@
package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import java.math.BigDecimal
fun List<WalletData>.findTotalBalanceState(): TotalBalance.State {
return this.mapToTotalBalanceState()
.reduce(TotalBalance.State::or)
fun List<WalletData>.findProgressState(): ProgressState {
return this
.mapToProgressState()
.reduce(ProgressState::or)
}
fun List<WalletData>.calculateTotalFiatAmount(): BigDecimal {
return this.map { it.currencyData.fiatAmount ?: BigDecimal.ZERO }
return this
.map { it.currencyData.fiatAmount ?: BigDecimal.ZERO }
.reduce(BigDecimal::plus)
}
fun List<WalletData>.mapToTotalBalanceState(): List<TotalBalance.State> {
private fun List<WalletData>.mapToProgressState(): List<ProgressState> {
return this.map {
when (it.currencyData.status) {
BalanceStatus.Refreshing -> ProgressState.Refreshing
BalanceStatus.VerifiedOnline,
BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress -> TotalBalance.State.Success
BalanceStatus.TransactionInProgress,
BalanceStatus.NoAccount -> ProgressState.Done
BalanceStatus.Unreachable,
BalanceStatus.NoAccount,
BalanceStatus.EmptyCard,
BalanceStatus.UnknownBlockchain -> TotalBalance.State.SomeTokensFailed
BalanceStatus.UnknownBlockchain -> ProgressState.Error
BalanceStatus.Loading,
null -> TotalBalance.State.Loading
null -> ProgressState.Loading
}
}
}
infix fun TotalBalance.State.or(newState: TotalBalance.State): TotalBalance.State {
private infix fun ProgressState.or(newState: ProgressState): ProgressState {
return when (this) {
TotalBalance.State.Loading -> when (newState) {
TotalBalance.State.Loading,
TotalBalance.State.SomeTokensFailed,
TotalBalance.State.Success -> this
ProgressState.Loading -> when (newState) {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done -> this
}
TotalBalance.State.Success,
TotalBalance.State.SomeTokensFailed -> when (newState) {
TotalBalance.State.Loading,
TotalBalance.State.SomeTokensFailed -> newState
TotalBalance.State.Success -> this
ProgressState.Done,
ProgressState.Error -> when (newState) {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error -> newState
ProgressState.Done -> this
}
ProgressState.Refreshing -> when (newState) {
ProgressState.Loading -> this
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done -> newState
}
}
}

View file

@ -25,7 +25,6 @@ import com.tangem.tap.features.wallet.redux.TradeCryptoState
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.WalletDialog
import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletStore
@ -135,14 +134,25 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
error = null,
)
}
is WalletAction.LoadData.Refresh -> {
newState = newState.copy(
state = ProgressState.Refreshing,
error = null,
)
}
is WalletAction.LoadWallet -> {
val balanceStatus = if (newState.state == ProgressState.Refreshing) {
BalanceStatus.Refreshing
} else {
BalanceStatus.Loading
}
if (action.blockchain == null) {
val wallets = newState.wallets.map {
it.copy(
walletsData = it.walletsData.map { walletData ->
walletData.copy(
currencyData = walletData.currencyData.copy(
status = BalanceStatus.Loading,
status = balanceStatus,
currency = walletData.currencyData.currency,
currencySymbol = walletData.currencyData.currencySymbol,
),
@ -169,7 +179,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
.map { wallet ->
wallet.copy(
currencyData = wallet.currencyData.copy(
status = BalanceStatus.Loading,
status = balanceStatus,
currency = wallet.currencyData.currency,
currencySymbol = wallet.currencyData.currencySymbol,
),

View file

@ -13,6 +13,7 @@ enum class BalanceStatus {
SameCurrencyTransactionInProgress,
Unreachable,
Loading,
Refreshing,
NoAccount,
EmptyCard,
UnknownBlockchain

View file

@ -135,15 +135,14 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
showWarningsIfPresent(state.mainWarningsList)
binding.srlWallet.isRefreshing = state.state == ProgressState.Refreshing
binding.srlWallet.setOnRefreshListener {
if (state.state != ProgressState.Loading) {
store.dispatch(WalletAction.LoadData)
if (state.state != ProgressState.Loading ||
state.state != ProgressState.Refreshing
) {
store.dispatch(WalletAction.LoadData.Refresh)
}
}
if (state.state != ProgressState.Loading) {
binding.srlWallet.isRefreshing = false
}
}
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {

View file

@ -64,6 +64,9 @@ class WalletAdapter
fun bind(wallet: WalletData) = with(binding) {
val status = wallet.currencyData.status
// Skip changes when on refreshing status
if (status == BalanceStatus.Refreshing) return@with
val isCustomCurrency = wallet.currency.isCustomCurrency(
derivationStyle = store.state.globalState
.scanResponse
@ -77,14 +80,13 @@ class WalletAdapter
BalanceStatus.Unreachable -> {
root.getString(R.string.wallet_balance_blockchain_unreachable)
}
BalanceStatus.NoAccount -> {
root.getString(R.string.wallet_error_no_account)
}
BalanceStatus.NoAccount,
BalanceStatus.VerifiedOnline,
BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.EmptyCard,
BalanceStatus.UnknownBlockchain,
BalanceStatus.Loading,
BalanceStatus.Refreshing,
null -> null
}
@ -114,9 +116,11 @@ class WalletAdapter
lContent.tvExchangeRate.text = if (isCustomCurrency) {
root.getString(id = R.string.token_item_no_rate)
} else {
wallet.fiatRateString
wallet.fiatRateString ?: ""
}
badgeCustomBalance.isVisible = isCustomCurrency
cardWallet.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.wallet.ui.wallet
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletDialog
import com.tangem.tap.store
@ -16,27 +15,25 @@ object CurrencySelectionDialog {
val currenciesToShow = dialog.currenciesList
.map { it.displayName }
.toTypedArray()
var currentSelection = dialog.currenciesList
val currentSelection = dialog.currenciesList
.indexOfFirst { it.code == dialog.currentAppCurrency.code }
return AlertDialog.Builder(context)
.setTitle(context.getString(R.string.details_row_title_currency))
.setNegativeButton(context.getString(R.string.common_cancel)) { _, _ -> /* no-op */ }
.setPositiveButton(context.getString(R.string.common_done)) { _, _ ->
dialog.currenciesList.getOrNull(currentSelection)?.let { selectedCurrency ->
.setOnDismissListener {
store.dispatch(WalletAction.DialogAction.Hide)
}
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
dialog.currenciesList.getOrNull(which)?.let { selectedCurrency ->
store.dispatch(
WalletAction.AppCurrencyAction.SelectAppCurrency(
fiatCurrency = selectedCurrency
)
)
store.dispatch(WalletAction.DialogAction.Hide)
}
}
.setOnDismissListener {
store.dispatchDialogHide()
}
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
currentSelection = which
}
.create()
}
}

View file

@ -14,6 +14,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.BalanceStatus
@ -125,16 +126,19 @@ class MultiWalletView : WalletView {
) = with(binding.lCardTotalBalance) {
root.isVisible = totalBalance != null
if (totalBalance != null) {
if (totalBalance.state == TotalBalance.State.Loading) {
// Skip changes when on refreshing state
if (totalBalance.state == ProgressState.Refreshing) return@with
if (totalBalance.state == ProgressState.Loading) {
veilBalance.veil()
} else {
veilBalance.unVeil()
}
tvProcessing.animateVisibility(
show = totalBalance.state == TotalBalance.State.SomeTokensFailed
show = totalBalance.state == ProgressState.Error
)
tvBalance.text = if (totalBalance.state == TotalBalance.State.SomeTokensFailed) ""
tvBalance.text = if (totalBalance.state == ProgressState.Error) ""
else totalBalance.fiatAmount.formatAmountAsSpannedString(
currencySymbol = totalBalance.fiatCurrency.symbol
)

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="12dp"
android:height="12dp" />
<stroke
android:width="2dp"
android:color="@color/white" />
<solid android:color="@color/darkGray1" />
</shape>

View file

@ -47,6 +47,15 @@
app:layout_constraintTop_toTopOf="@id/iv_currency"
tools:text="J" />
<View
android:id="@+id/badge_custom_balance"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/shape_badge_custom_currency"
android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/iv_currency"
app:layout_constraintEnd_toEndOf="@id/iv_currency" />
<include
layout="@layout/item_currency_wallet_content"
android:id="@+id/l_content"