[#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:
parent
fb5d446bab
commit
786c8c2cab
|
@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Sending zero funds is allowed only for shielded recipient address type
|
- Sending zero funds is allowed only for shielded recipient address type
|
||||||
|
- 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
|
## [0.2.0 (609)] - 2024-04-18
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ data class WalletSnapshot(
|
||||||
val isSendEnabled: Boolean get() = hasSaplingFunds && hasOrchardFunds
|
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
|
// 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.
|
// 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
|
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
|
// 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.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
|
// 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.valuePendingBalance() = orchardBalance.valuePending + saplingBalance.valuePending
|
||||||
|
|
||||||
|
fun WalletSnapshot.hasValuePending() = valuePendingBalance().value > 0L
|
||||||
|
|
|
@ -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.OnboardingState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
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.spendableBalance
|
||||||
import co.electriccoin.zcash.ui.common.model.totalBalance
|
import co.electriccoin.zcash.ui.common.model.totalBalance
|
||||||
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
|
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
|
* A flow of the wallet balances state used for the UI layer. It's computed form [WalletSnapshot]'s properties
|
||||||
* [WalletRestoringState] and provides the correct [BalanceState] UI state.
|
* and provides the result [BalanceState] UI state.
|
||||||
*/
|
*/
|
||||||
val balanceState: StateFlow<BalanceState> =
|
val balanceState: StateFlow<BalanceState> =
|
||||||
walletSnapshot
|
walletSnapshot
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.combine(walletRestoringState) {
|
.map { snapshot ->
|
||||||
walletSnapshot: WalletSnapshot, walletRestoringState: WalletRestoringState ->
|
when {
|
||||||
when (walletRestoringState) {
|
// Show the loader only under these conditions:
|
||||||
WalletRestoringState.NONE -> BalanceState.None
|
// - Available balance is currently zero
|
||||||
WalletRestoringState.INITIATING ->
|
// - Wallet has some ChangePending in progress
|
||||||
BalanceState.Available(
|
// - And Total balance is non-zero
|
||||||
totalBalance = walletSnapshot.totalBalance(),
|
(
|
||||||
spendableBalance = walletSnapshot.spendableBalance()
|
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(
|
}.stateIn(
|
||||||
|
|
|
@ -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.WalletRestoringState
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
import co.electriccoin.zcash.ui.common.model.changePendingBalance
|
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.spendableBalance
|
||||||
import co.electriccoin.zcash.ui.common.model.valuePendingBalance
|
import co.electriccoin.zcash.ui.common.model.valuePendingBalance
|
||||||
import co.electriccoin.zcash.ui.common.test.CommonTag
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
|
@ -569,8 +571,6 @@ fun ChangePendingRow(walletSnapshot: WalletSnapshot) {
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val changePendingHasValue = walletSnapshot.changePendingBalance().value > 0L
|
|
||||||
|
|
||||||
StyledBalance(
|
StyledBalance(
|
||||||
balanceString = walletSnapshot.changePendingBalance().toZecString(),
|
balanceString = walletSnapshot.changePendingBalance().toZecString(),
|
||||||
textStyles =
|
textStyles =
|
||||||
|
@ -584,7 +584,7 @@ fun ChangePendingRow(walletSnapshot: WalletSnapshot) {
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
|
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
|
||||||
if (changePendingHasValue) {
|
if (walletSnapshot.hasChangePending()) {
|
||||||
CircularSmallProgressIndicator()
|
CircularSmallProgressIndicator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -605,8 +605,6 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val valuePendingHasValue = walletSnapshot.valuePendingBalance().value > 0L
|
|
||||||
|
|
||||||
StyledBalance(
|
StyledBalance(
|
||||||
balanceString = walletSnapshot.valuePendingBalance().toZecString(),
|
balanceString = walletSnapshot.valuePendingBalance().toZecString(),
|
||||||
textStyles =
|
textStyles =
|
||||||
|
@ -620,7 +618,7 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
|
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
|
||||||
if (valuePendingHasValue) {
|
if (walletSnapshot.hasValuePending()) {
|
||||||
CircularSmallProgressIndicator()
|
CircularSmallProgressIndicator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue