diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/model/WalletAccount.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/model/WalletAccount.kt index 8cc1f40de..41aa54afc 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/model/WalletAccount.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/model/WalletAccount.kt @@ -25,29 +25,46 @@ sealed interface WalletAccount : Comparable { 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, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/FlexaRepository.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/FlexaRepository.kt index 4b4b07dcc..d5ebe71ce 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/FlexaRepository.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/FlexaRepository.kt @@ -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() diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/ShieldFundsRepository.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/ShieldFundsRepository.kt index 788d8bf5b..d3e2d1012 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/ShieldFundsRepository.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/ShieldFundsRepository.kt @@ -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( diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/BalanceWidgetViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/BalanceWidgetViewModel.kt index 4aaa5645e..4ed29a219 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/BalanceWidgetViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/BalanceWidgetViewModel.kt @@ -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) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionView.kt index 2ac511e03..ec0812107 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionView.kt @@ -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) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionViewModel.kt index 977061b74..7af37e7bb 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionViewModel.kt @@ -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() diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/seed/RestoreSeedTag.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/seed/RestoreSeedTag.kt index 06f1abf68..3dd8a88f2 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/seed/RestoreSeedTag.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/restore/seed/RestoreSeedTag.kt @@ -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" } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt index 01ff3595f..dcbfa0db1 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt @@ -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( diff --git a/ui-lib/src/main/res/ui/common/values-es/strings.xml b/ui-lib/src/main/res/ui/common/values-es/strings.xml index dbee46c8f..857881dcc 100644 --- a/ui-lib/src/main/res/ui/common/values-es/strings.xml +++ b/ui-lib/src/main/res/ui/common/values-es/strings.xml @@ -34,4 +34,6 @@ two days two weeks two months + %S ZEC + Dismiss diff --git a/ui-lib/src/main/res/ui/common/values/strings.xml b/ui-lib/src/main/res/ui/common/values/strings.xml index 3bb570da0..34a46f019 100644 --- a/ui-lib/src/main/res/ui/common/values/strings.xml +++ b/ui-lib/src/main/res/ui/common/values/strings.xml @@ -35,6 +35,6 @@ two days two weeks two months - %S ZEC + Dismiss diff --git a/ui-lib/src/main/res/ui/home/values-es/strings.xml b/ui-lib/src/main/res/ui/home/values-es/strings.xml index 8d7fd0a6d..1ad13e2e4 100644 --- a/ui-lib/src/main/res/ui/home/values-es/strings.xml +++ b/ui-lib/src/main/res/ui/home/values-es/strings.xml @@ -70,4 +70,10 @@ Help improve Zashi Allow sharing crash reports Review + Spendable Balance + All your funds are shielded and spendable. + Pending transactions are getting mined and confirmed. + 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) + Shielded ZEC (Spendable) + Pending \ No newline at end of file diff --git a/ui-lib/src/main/res/ui/home/values/strings.xml b/ui-lib/src/main/res/ui/home/values/strings.xml index 8d7fd0a6d..1ad13e2e4 100644 --- a/ui-lib/src/main/res/ui/home/values/strings.xml +++ b/ui-lib/src/main/res/ui/home/values/strings.xml @@ -70,4 +70,10 @@ Help improve Zashi Allow sharing crash reports Review + Spendable Balance + All your funds are shielded and spendable. + Pending transactions are getting mined and confirmed. + 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) + Shielded ZEC (Spendable) + Pending \ No newline at end of file