Balance actions bugfixes

This commit is contained in:
Milan Cerovsky 2025-04-15 22:09:58 +02:00
parent 64af1a3d50
commit 2fe90bcc43
12 changed files with 134 additions and 84 deletions

View File

@ -25,29 +25,46 @@ sealed interface WalletAccount : Comparable<WalletAccount> {
val hdAccountIndex: Zip32AccountIndex
get() = sdkAccount.hdAccountIndex!!
/**
* Total transparent + total shielded balance.
*/
val totalBalance: Zatoshi
/**
* Total shielded balance including non-spendable.
*/
val totalShieldedBalance: Zatoshi
val spendableBalance: Zatoshi
val changePendingBalance: Zatoshi
val valuePendingBalance: Zatoshi
val pendingBalance: Zatoshi
get() = changePendingBalance + valuePendingBalance
/**
* Total spendable transparent balance.
*/
val totalTransparentBalance: Zatoshi
val hasChangePending: Boolean
val hasValuePending: Boolean
val isPending: Boolean
get() = pendingBalance > Zatoshi(0)
/**
* Spendable & available shielded balance. Might be smaller than total shielded balance.
*/
val spendableShieldedBalance: Zatoshi
fun canSpend(amount: Zatoshi): Boolean = spendableBalance >= amount
/**
* Pending shielded Balance.
*/
val pendingShieldedBalance: Zatoshi
fun isProcessingZeroAvailableBalance(): Boolean {
if (totalShieldedBalance == Zatoshi(0) && transparent.balance > Zatoshi(0)) {
return false
val isShieldedPending: Boolean
get() = pendingShieldedBalance > Zatoshi(0)
val isShieldingAvailable: Boolean
get() = totalTransparentBalance > Zatoshi(100000L)
val isProcessingZeroSpendableBalance: Boolean
get() {
if (totalShieldedBalance == Zatoshi(0) && totalTransparentBalance > Zatoshi(0)) {
return false
}
return totalBalance > Zatoshi(0) && totalShieldedBalance == Zatoshi(0)
}
return totalBalance != totalShieldedBalance && totalShieldedBalance == Zatoshi(0)
}
fun canSpend(amount: Zatoshi): Boolean = spendableShieldedBalance >= amount
}
data class ZashiAccount(
@ -59,22 +76,28 @@ data class ZashiAccount(
) : WalletAccount {
override val name: StringResource
get() = stringRes(co.electriccoin.zcash.ui.R.string.zashi_wallet_name)
override val icon: Int
get() = R.drawable.ic_item_zashi
override val totalBalance: Zatoshi
get() = unified.balance.total + sapling.balance.total + transparent.balance
override val totalShieldedBalance: Zatoshi
get() = unified.balance.total + sapling.balance.total
override val spendableBalance: Zatoshi
override val totalTransparentBalance: Zatoshi
get() = transparent.balance
override val spendableShieldedBalance: Zatoshi
get() = unified.balance.available + sapling.balance.available
override val changePendingBalance: Zatoshi
get() = unified.balance.changePending + sapling.balance.changePending
override val valuePendingBalance: Zatoshi
get() = unified.balance.valuePending + sapling.balance.valuePending
override val hasChangePending: Boolean
get() = changePendingBalance.value > 0L
override val hasValuePending: Boolean
get() = valuePendingBalance.value > 0L
override val pendingShieldedBalance: Zatoshi
get() {
val changePendingShieldedBalance = unified.balance.changePending + sapling.balance.changePending
val valuePendingShieldedBalance = unified.balance.valuePending + sapling.balance.valuePending
return changePendingShieldedBalance + valuePendingShieldedBalance
}
override fun compareTo(other: WalletAccount) =
when (other) {
@ -91,23 +114,26 @@ data class KeystoneAccount(
) : WalletAccount {
override val icon: Int
get() = R.drawable.ic_item_keystone
override val name: StringResource
get() = stringRes(co.electriccoin.zcash.ui.R.string.keystone_wallet_name)
override val sapling: SaplingInfo? = null
override val totalBalance: Zatoshi
get() = unified.balance.total + transparent.balance
override val totalShieldedBalance: Zatoshi
get() = unified.balance.total
override val spendableBalance: Zatoshi
override val totalTransparentBalance: Zatoshi
get() = transparent.balance
override val spendableShieldedBalance: Zatoshi
get() = unified.balance.available
override val changePendingBalance: Zatoshi
get() = unified.balance.changePending
override val valuePendingBalance: Zatoshi
get() = unified.balance.valuePending
override val hasChangePending: Boolean
get() = changePendingBalance.value > 0L
override val hasValuePending: Boolean
get() = valuePendingBalance.value > 0L
override val pendingShieldedBalance: Zatoshi
get() = unified.balance.changePending + unified.balance.valuePending
override fun compareTo(other: WalletAccount) =
when (other) {
@ -124,10 +150,7 @@ data class UnifiedInfo(
data class TransparentInfo(
val address: WalletAddress.Transparent,
val balance: Zatoshi
) {
val isShieldingAvailable: Boolean
get() = balance > Zatoshi(100000L)
}
)
data class SaplingInfo(
val address: WalletAddress.Sapling,

View File

@ -42,7 +42,7 @@ class FlexaRepositoryImpl(
Twig.info { "Flexa initialized" }
observeZashiAccountUseCase()
.map { it?.totalShieldedBalance to it?.spendableBalance }
.map { it?.totalShieldedBalance to it?.spendableShieldedBalance }
.collect { (total, available) ->
val totalZec = total.convertZatoshiToZec().toDouble()
val availableZec = available.convertZatoshiToZec().toDouble()

View File

@ -29,7 +29,7 @@ class ShieldFundsRepositoryImpl(
account == null ->
flowOf(ShieldFundsData.Unavailable)
account.transparent.isShieldingAvailable ->
account.isShieldingAvailable ->
shieldFundsDataSource.observe(account.sdkAccount.accountUuid).map {
when (it) {
is ShieldFundsAvailability.Available -> ShieldFundsData.Available(

View File

@ -40,17 +40,15 @@ class BalanceWidgetViewModel(
)
)
private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState): BalanceWidgetState {
return BalanceWidgetState(
private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState) =
BalanceWidgetState(
totalBalance = account?.totalBalance ?: Zatoshi(0),
exchangeRate = if (args.isExchangeRateButtonEnabled) exchangeRateUsd else null,
button = when {
!args.isBalanceButtonEnabled -> null
account == null -> null
account.totalBalance == account.spendableBalance -> null
account.isProcessingZeroAvailableBalance() && !account.isPending ->
account.totalBalance == account.spendableShieldedBalance -> null
account.totalBalance > account.spendableShieldedBalance && account.isShieldedPending ->
BalanceButtonState(
icon = R.drawable.ic_balances_expand,
text = stringRes(R.string.widget_balances_button_spendable),
@ -58,10 +56,10 @@ class BalanceWidgetViewModel(
onClick = ::onBalanceButtonClick
)
account.totalBalance > account.spendableBalance -> BalanceButtonState(
account.totalBalance > account.spendableShieldedBalance -> BalanceButtonState(
icon = R.drawable.ic_balances_expand,
text = stringRes(R.string.widget_balances_button_spendable),
amount = account.spendableBalance,
amount = account.spendableShieldedBalance,
onClick = ::onBalanceButtonClick
)
@ -69,7 +67,6 @@ class BalanceWidgetViewModel(
},
showDust = args.showDust
)
}
private fun onBalanceButtonClick() = navigationRouter.forward(BalanceAction)
}

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.R
@ -62,10 +63,12 @@ fun BottomSheetContent(state: BalanceActionState, modifier: Modifier = Modifier)
.padding(horizontal = 24.dp)
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = state.title.getValue(),
color = ZashiColors.Text.textPrimary,
style = ZashiTypography.textXl,
fontWeight = FontWeight.SemiBold
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center
)
Spacer(12.dp)
Text(
@ -80,8 +83,8 @@ fun BottomSheetContent(state: BalanceActionState, modifier: Modifier = Modifier)
}
BalanceActionRow(state)
}
Spacer(32.dp)
state.shieldButton?.let {
Spacer(32.dp)
BalanceShieldButton(it)
}
Spacer(32.dp)

View File

@ -37,7 +37,7 @@ class BalanceActionViewModel(
if (account == null) return null
return BalanceActionState(
title = stringRes("Spendable Balance"),
title = stringRes(R.string.balance_action_title),
message = createMessage(account),
positive = createPositiveButton(account),
onBack = ::onBack,
@ -48,50 +48,58 @@ class BalanceActionViewModel(
private fun createMessage(account: WalletAccount): StringResource {
val pending = when {
account.totalBalance == account.spendableBalance && !account.isPending ->
stringRes("All your funds are shielded and spendable.")
account.totalShieldedBalance == account.spendableShieldedBalance ->
stringRes(R.string.balance_action_all_shielded)
account.totalShieldedBalance > account.spendableShieldedBalance ->
stringRes(R.string.balance_action_pending)
account.isPending || account.isProcessingZeroAvailableBalance() ->
stringRes("Pending transactions are getting mined and confirmed.")
else -> null
}
val shielding =
stringRes("Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)")
.takeIf { account.transparent.isShieldingAvailable }
val shielding = stringRes(R.string.balance_action_shield_message).takeIf { account.isShieldingAvailable }
return if (pending != null && shielding != null) {
pending + stringRes("\n\n") + shielding
} else {
listOfNotNull(pending, shielding).reduceOrNull { acc, stringResource -> acc + stringResource } ?: stringRes(
""
)
pending ?: shielding ?: stringRes("")
}
}
private fun createPositiveButton(account: WalletAccount) = ButtonState(
text = if (account.transparent.isShieldingAvailable) stringRes("Dismiss") else stringRes("Ok"),
text = if (account.isShieldingAvailable) {
stringRes(R.string.general_dismiss)
} else {
stringRes(R.string.general_ok)
},
onClick = ::onBack
)
private fun createInfoRows(account: WalletAccount) = listOfNotNull(
BalanceActionRowState(
title = stringRes("Shielded ZEC (Spendable)"),
title = stringRes(R.string.balance_action_info_shielded),
icon = imageRes(R.drawable.ic_balance_shield),
value = stringRes(R.string.general_zec, stringRes(account.spendableBalance))
value = stringRes(R.string.general_zec, stringRes(account.spendableShieldedBalance))
),
if (!account.isProcessingZeroAvailableBalance()) {
BalanceActionRowState(
title = stringRes("Pending"),
icon = loadingImageRes(),
value = stringRes(R.string.general_zec, stringRes(account.totalBalance))
)
} else {
BalanceActionRowState(
title = stringRes("Pending"),
icon = loadingImageRes(),
value = stringRes(R.string.general_zec, stringRes(account.pendingBalance))
).takeIf { account.isPending }
when {
account.totalShieldedBalance > account.spendableShieldedBalance && account.isShieldedPending ->
BalanceActionRowState(
title = stringRes(R.string.balance_action_info_pending),
icon = loadingImageRes(),
value = stringRes(R.string.general_zec, stringRes(account.pendingShieldedBalance))
)
account.totalShieldedBalance > account.spendableShieldedBalance ->
BalanceActionRowState(
title = stringRes(R.string.balance_action_info_pending),
icon = loadingImageRes(),
value = stringRes(
R.string.general_zec,
stringRes(account.totalShieldedBalance - account.spendableShieldedBalance)
)
)
else -> null
},
)
@ -99,7 +107,7 @@ class BalanceActionViewModel(
return BalanceShieldButtonState(
amount = account.transparent.balance,
onShieldClick = ::onShieldClick
).takeIf { account.transparent.isShieldingAvailable }
).takeIf { account.isShieldingAvailable }
}
private fun onBack() = navigationRouter.back()

View File

@ -5,8 +5,6 @@ package co.electriccoin.zcash.ui.screen.restore.seed
*/
object RestoreSeedTag {
const val SEED_WORD_TEXT_FIELD = "seed_text_field"
const val BIRTHDAY_TEXT_FIELD = "birthday_text_field"
const val CHIP_LAYOUT = "chip_group"
const val AUTOCOMPLETE_LAYOUT = "autocomplete_layout"
const val AUTOCOMPLETE_ITEM = "autocomplete_item"
}

View File

@ -331,7 +331,7 @@ private fun SendForm(
recipientAddressState.type is AddressType.Valid &&
recipientAddressState.type !is AddressType.Transparent &&
recipientAddressState.type !is AddressType.Tex
)
)
SendFormAmountTextField(
amountState = amountState,
@ -430,10 +430,13 @@ fun SendButton(
AddressType.Tex ->
WalletAddress.Tex.new(recipientAddressState.address)
AddressType.Transparent ->
WalletAddress.Transparent.new(recipientAddressState.address)
AddressType.Unified ->
WalletAddress.Unified.new(recipientAddressState.address)
null -> WalletAddress.Unified.new(recipientAddressState.address)
}
)
@ -604,7 +607,7 @@ fun SendFormAmountTextField(
}
is AmountState.Valid -> {
if (selectedAccount.spendableBalance < amountState.zatoshi) {
if (selectedAccount.spendableShieldedBalance < amountState.zatoshi) {
stringResource(id = R.string.send_amount_insufficient_balance)
} else {
null
@ -630,7 +633,9 @@ fun SendFormAmountTextField(
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
Row {
Row(
modifier = Modifier.fillMaxWidth()
) {
ZashiTextField(
singleLine = true,
maxLines = 1,
@ -648,7 +653,9 @@ fun SendFormAmountTextField(
)
},
modifier = Modifier.weight(1f),
innerModifier = Modifier.testTag(SendTag.SEND_AMOUNT_FIELD),
innerModifier = ZashiTextFieldDefaults
.innerModifier
.testTag(SendTag.SEND_AMOUNT_FIELD),
error = amountError,
placeholder = {
Text(

View File

@ -34,4 +34,6 @@
<string name="general_remind_me_in_two_days">two days</string>
<string name="general_remind_me_in_two_weeks">two weeks</string>
<string name="general_remind_me_in_two_months">two months</string>
<string name="general_zec">%S ZEC</string>
<string name="general_dismiss">Dismiss</string>
</resources>

View File

@ -35,6 +35,6 @@
<string name="general_remind_me_in_two_days">two days</string>
<string name="general_remind_me_in_two_weeks">two weeks</string>
<string name="general_remind_me_in_two_months">two months</string>
<string name="general_zec">%S ZEC</string>
<string name="general_dismiss">Dismiss</string>
</resources>

View File

@ -70,4 +70,10 @@
<string name="home_message_crash_reporting_title">Help improve Zashi</string>
<string name="home_message_crash_reporting_subtitle">Allow sharing crash reports</string>
<string name="home_message_crash_reporting_button">Review</string>
<string name="balance_action_title">Spendable Balance</string>
<string name="balance_action_all_shielded">All your funds are shielded and spendable.</string>
<string name="balance_action_pending">Pending transactions are getting mined and confirmed.</string>
<string name="balance_action_shield_message">Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)</string>
<string name="balance_action_info_shielded">Shielded ZEC (Spendable)</string>
<string name="balance_action_info_pending">Pending</string>
</resources>

View File

@ -70,4 +70,10 @@
<string name="home_message_crash_reporting_title">Help improve Zashi</string>
<string name="home_message_crash_reporting_subtitle">Allow sharing crash reports</string>
<string name="home_message_crash_reporting_button">Review</string>
<string name="balance_action_title">Spendable Balance</string>
<string name="balance_action_all_shielded">All your funds are shielded and spendable.</string>
<string name="balance_action_pending">Pending transactions are getting mined and confirmed.</string>
<string name="balance_action_shield_message">Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)</string>
<string name="balance_action_info_shielded">Shielded ZEC (Spendable)</string>
<string name="balance_action_info_pending">Pending</string>
</resources>