[#1371] Improve Balances widget loader logic

- Closes #1371
- This also covers the security audit recommendation: 4.2.14 (App) This comment in WalletSnapshot.kt should be ticketed
This commit is contained in:
Honza 2024-04-19 13:24:41 +02:00
parent 533f335038
commit 57cd97145c
4 changed files with 35 additions and 29 deletions

View File

@ -9,6 +9,9 @@ directly impact users rather than highlighting other key architectural updates.*
## [Unreleased]
### Fixed
- The Balances widget loader has been improved to better handle cases, like a wallet with only transparent funds
## [0.2.0 (609)] - 2024-04-18
### Added

View File

@ -34,6 +34,8 @@ data class WalletSnapshot(
val isSendEnabled: Boolean get() = hasSaplingFunds && hasOrchardFunds
}
// TODO [#1370]: WalletSnapshot.canSpend() calculation limitation
// TODO [#1370]: https://github.com/Electric-Coin-Company/zashi-android/issues/1370
// Note this check is not entirely correct - it does not calculate the resulting fee using the new Proposal API. It's
// fine for now, but it's subject to improvement later once we figure out how to handle it in such cases.
fun WalletSnapshot.canSpend(amount: Zatoshi): Boolean = spendableBalance() >= amount
@ -48,5 +50,9 @@ fun WalletSnapshot.spendableBalance() = orchardBalance.available + saplingBalanc
// Note that summing both values could be confusing, and we might prefer dividing them in the future
fun WalletSnapshot.changePendingBalance() = orchardBalance.changePending + saplingBalance.changePending
fun WalletSnapshot.hasChangePending() = changePendingBalance().value > 0L
// Note that summing both values could be confusing, and we might prefer dividing them in the future
fun WalletSnapshot.valuePendingBalance() = orchardBalance.valuePending + saplingBalance.valuePending
fun WalletSnapshot.hasValuePending() = valuePendingBalance().value > 0L

View File

@ -32,6 +32,7 @@ import co.electriccoin.zcash.ui.common.extension.throttle
import co.electriccoin.zcash.ui.common.model.OnboardingState
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.hasChangePending
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.totalBalance
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
@ -242,34 +243,32 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
)
/**
* A flow of the wallet balances state used for the UI layer. It combines [WalletSnapshot] with
* [WalletRestoringState] and provides the correct [BalanceState] UI state.
* A flow of the wallet balances state used for the UI layer. It's computed form [WalletSnapshot]'s properties
* and provides the result [BalanceState] UI state.
*/
val balanceState: StateFlow<BalanceState> =
walletSnapshot
.filterNotNull()
.combine(walletRestoringState) {
walletSnapshot: WalletSnapshot, walletRestoringState: WalletRestoringState ->
when (walletRestoringState) {
WalletRestoringState.NONE -> BalanceState.None
WalletRestoringState.INITIATING ->
BalanceState.Available(
totalBalance = walletSnapshot.totalBalance(),
spendableBalance = walletSnapshot.spendableBalance()
.map { snapshot ->
when {
// Show the loader only under these conditions:
// - Available balance is currently zero
// - Wallet has some ChangePending in progress
// - And Total balance is non-zero
(
snapshot.spendableBalance().value == 0L &&
snapshot.hasChangePending() &&
snapshot.totalBalance().value > 0L
) -> {
BalanceState.Loading(
totalBalance = snapshot.totalBalance()
)
}
else -> {
BalanceState.Available(
totalBalance = snapshot.totalBalance(),
spendableBalance = snapshot.spendableBalance()
)
WalletRestoringState.RESTORING, WalletRestoringState.SYNCING -> {
if (walletSnapshot.spendableBalance().value == 0L &&
walletSnapshot.totalBalance().value > 0L
) {
BalanceState.Loading(
totalBalance = walletSnapshot.totalBalance()
)
} else {
BalanceState.Available(
totalBalance = walletSnapshot.totalBalance(),
spendableBalance = walletSnapshot.spendableBalance()
)
}
}
}
}.stateIn(

View File

@ -57,6 +57,8 @@ import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.changePendingBalance
import co.electriccoin.zcash.ui.common.model.hasChangePending
import co.electriccoin.zcash.ui.common.model.hasValuePending
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.valuePendingBalance
import co.electriccoin.zcash.ui.common.test.CommonTag
@ -569,8 +571,6 @@ fun ChangePendingRow(walletSnapshot: WalletSnapshot) {
)
Row(verticalAlignment = Alignment.CenterVertically) {
val changePendingHasValue = walletSnapshot.changePendingBalance().value > 0L
StyledBalance(
balanceString = walletSnapshot.changePendingBalance().toZecString(),
textStyles =
@ -584,7 +584,7 @@ fun ChangePendingRow(walletSnapshot: WalletSnapshot) {
Spacer(modifier = Modifier.width(12.dp))
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
if (changePendingHasValue) {
if (walletSnapshot.hasChangePending()) {
CircularSmallProgressIndicator()
}
}
@ -605,8 +605,6 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
)
Row(verticalAlignment = Alignment.CenterVertically) {
val valuePendingHasValue = walletSnapshot.valuePendingBalance().value > 0L
StyledBalance(
balanceString = walletSnapshot.valuePendingBalance().toZecString(),
textStyles =
@ -620,7 +618,7 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
Spacer(modifier = Modifier.width(12.dp))
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
if (valuePendingHasValue) {
if (walletSnapshot.hasValuePending()) {
CircularSmallProgressIndicator()
}
}