From e80f583d54dce9133969d882c1ceeea539bcd85c Mon Sep 17 00:00:00 2001 From: Milan Cerovsky Date: Wed, 23 Apr 2025 11:33:59 +0200 Subject: [PATCH] Messages and balances hotfixes --- .../cash/z/ecc/sdk/extension/ZatoshiExt.kt | 4 ++ .../co/electriccoin/zcash/di/UseCaseModule.kt | 4 +- .../electriccoin/zcash/di/ViewModelModule.kt | 6 +- .../co/electriccoin/zcash/ui/Navigation.kt | 8 +-- .../zcash/ui/common/model/WalletAccount.kt | 12 ++++ .../common/provider/SynchronizerProvider.kt | 3 +- .../repository/TransactionRepository.kt | 3 + .../ui/common/repository/WalletRepository.kt | 6 -- ...nsUseCase.kt => GetTransactionsUseCase.kt} | 2 +- .../screen/balances/BalanceWidgetViewModel.kt | 12 +--- .../AndroidSpendableBalance.kt} | 10 +-- .../SpendableBalanceState.kt} | 12 ++-- .../SpendableBalanceView.kt} | 22 +++--- .../SpendableBalanceViewModel.kt} | 68 ++++++++++++------- .../restoring/AndroidWalletRestoringInfo.kt | 33 +++++---- .../restoring/WalletRestoringInfoState.kt | 11 +++ .../restoring/WalletRestoringInfoViewModel.kt | 37 ++++++++++ .../home/shieldfunds/ShieldFundsInfoView.kt | 6 +- .../transactiondetail/info/ReceiveShielded.kt | 11 +-- .../info/ReceiveTransparent.kt | 11 +-- .../transactiondetail/info/SendShielded.kt | 11 +-- .../transactiondetail/info/SendTransparent.kt | 11 +-- .../transactiondetail/info/Shielding.kt | 11 +-- .../TransactionHistoryWidgetViewModel.kt | 6 +- .../main/res/ui/home/values-es/strings.xml | 7 +- .../src/main/res/ui/home/values/strings.xml | 7 +- 26 files changed, 212 insertions(+), 122 deletions(-) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/{GetCurrentTransactionsUseCase.kt => GetTransactionsUseCase.kt} (97%) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/{action/AndroidBalanceAction.kt => spendable/AndroidSpendableBalance.kt} (66%) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/{action/BalanceActionState.kt => spendable/SpendableBalanceState.kt} (70%) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/{action/BalanceActionView.kt => spendable/SpendableBalanceView.kt} (92%) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/{action/BalanceActionViewModel.kt => spendable/SpendableBalanceViewModel.kt} (63%) create mode 100644 ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoState.kt create mode 100644 ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoViewModel.kt diff --git a/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt index cd6dbe039..a1cea68b4 100644 --- a/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt +++ b/sdk-ext-lib/src/main/java/cash/z/ecc/sdk/extension/ZatoshiExt.kt @@ -38,3 +38,7 @@ data class ZecAmountPair( val main: String, val suffix: String ) + +@Suppress("MagicNumber") +val Zatoshi.Companion.typicalFee: Zatoshi + get() = Zatoshi(100000) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt index c797e6b95..fc1362db8 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt @@ -19,7 +19,6 @@ import co.electriccoin.zcash.ui.common.usecase.GetCoinbaseStatusUseCase import co.electriccoin.zcash.ui.common.usecase.GetConfigurationUseCase import co.electriccoin.zcash.ui.common.usecase.GetContactByAddressUseCase import co.electriccoin.zcash.ui.common.usecase.GetCurrentFilteredTransactionsUseCase -import co.electriccoin.zcash.ui.common.usecase.GetCurrentTransactionsUseCase import co.electriccoin.zcash.ui.common.usecase.GetExchangeRateUseCase import co.electriccoin.zcash.ui.common.usecase.GetFlexaStatusUseCase import co.electriccoin.zcash.ui.common.usecase.GetHomeMessageUseCase @@ -34,6 +33,7 @@ import co.electriccoin.zcash.ui.common.usecase.GetSynchronizerUseCase import co.electriccoin.zcash.ui.common.usecase.GetTransactionDetailByIdUseCase import co.electriccoin.zcash.ui.common.usecase.GetTransactionFiltersUseCase import co.electriccoin.zcash.ui.common.usecase.GetTransactionMetadataUseCase +import co.electriccoin.zcash.ui.common.usecase.GetTransactionsUseCase import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletAccountsUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase @@ -142,7 +142,7 @@ val useCaseModule = factoryOf(::GetSelectedWalletAccountUseCase) singleOf(::ObserveClearSendUseCase) singleOf(::PrefillSendUseCase) - factoryOf(::GetCurrentTransactionsUseCase) + factoryOf(::GetTransactionsUseCase) factoryOf(::GetCurrentFilteredTransactionsUseCase) onClose ::closeableCallback factoryOf(::CreateProposalUseCase) factoryOf(::OnZip321ScannedUseCase) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt index e52e3eda3..79c213692 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt @@ -9,7 +9,7 @@ import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.AddressBookViewMode import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.SelectRecipientViewModel import co.electriccoin.zcash.ui.screen.advancedsettings.AdvancedSettingsViewModel import co.electriccoin.zcash.ui.screen.balances.BalanceWidgetViewModel -import co.electriccoin.zcash.ui.screen.balances.action.BalanceActionViewModel +import co.electriccoin.zcash.ui.screen.balances.spendable.SpendableBalanceViewModel import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel @@ -23,6 +23,7 @@ import co.electriccoin.zcash.ui.screen.home.HomeViewModel import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupDetailViewModel import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupInfoViewModel import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportOptInViewModel +import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfoViewModel import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfoViewModel import co.electriccoin.zcash.ui.screen.integrations.IntegrationsViewModel import co.electriccoin.zcash.ui.screen.qrcode.viewmodel.QrCodeViewModel @@ -156,6 +157,7 @@ val viewModelModule = viewModelOf(::ExchangeRateSettingsViewModel) viewModelOf(::WalletBackupDetailViewModel) viewModelOf(::ErrorViewModel) - viewModelOf(::BalanceActionViewModel) + viewModelOf(::SpendableBalanceViewModel) viewModelOf(::CrashReportOptInViewModel) + viewModelOf(::WalletRestoringInfoViewModel) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt index 40ac17e2d..4bfd069e0 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt @@ -49,8 +49,8 @@ import co.electriccoin.zcash.ui.screen.addressbook.WrapAddressBook import co.electriccoin.zcash.ui.screen.advancedsettings.WrapAdvancedSettings import co.electriccoin.zcash.ui.screen.authentication.AuthenticationUseCase import co.electriccoin.zcash.ui.screen.authentication.WrapAuthentication -import co.electriccoin.zcash.ui.screen.balances.action.AndroidBalanceAction -import co.electriccoin.zcash.ui.screen.balances.action.BalanceAction +import co.electriccoin.zcash.ui.screen.balances.spendable.AndroidSpendableBalance +import co.electriccoin.zcash.ui.screen.balances.spendable.SpendableBalance import co.electriccoin.zcash.ui.screen.chooseserver.WrapChooseServer import co.electriccoin.zcash.ui.screen.connectkeystone.AndroidConnectKeystone import co.electriccoin.zcash.ui.screen.connectkeystone.ConnectKeystone @@ -497,14 +497,14 @@ internal fun MainActivity.Navigation() { ) { AndroidErrorBottomSheet() } - dialog( + dialog( dialogProperties = DialogProperties( dismissOnBackPress = false, dismissOnClickOutside = false ) ) { - AndroidBalanceAction() + AndroidSpendableBalance() } composable { AndroidCrashReportOptIn() 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 077f24440..dc8c69902 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 @@ -57,6 +57,18 @@ sealed interface WalletAccount : Comparable { val isShieldingAvailable: Boolean get() = totalTransparentBalance > Zatoshi(100000L) + val isAllShielded: Boolean + get() { + val isAllShielded = totalBalance == spendableShieldedBalance + val isAllShieldedWithTransparentDustLeft = + totalBalance > spendableShieldedBalance && + spendableShieldedBalance == totalShieldedBalance && + totalTransparentBalance > Zatoshi(0) && + !isShieldingAvailable + + return isAllShielded || isAllShieldedWithTransparentDustLeft + } + fun canSpend(amount: Zatoshi): Boolean = spendableShieldedBalance >= amount } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/SynchronizerProvider.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/SynchronizerProvider.kt index 26c23f744..8f578612e 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/SynchronizerProvider.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/provider/SynchronizerProvider.kt @@ -48,8 +48,7 @@ class SynchronizerProviderImpl( emit(synchronizer) } } - } - .flowOn(Dispatchers.IO) + }.flowOn(Dispatchers.IO) .stateIn( scope = scope, started = SharingStarted.Lazily, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/TransactionRepository.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/TransactionRepository.kt index 3560dfbfb..23ae6dd5e 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/TransactionRepository.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/TransactionRepository.kt @@ -415,3 +415,6 @@ sealed interface ShieldTransaction : Transaction { override val overview: TransactionOverview, ) : ShieldTransaction } + +val Transaction.isPending: Boolean + get() = this is SendTransaction.Pending || this is ShieldTransaction.Pending || this is ReceiveTransaction.Pending diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt index 2a62be774..ff6b2488c 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/repository/WalletRepository.kt @@ -15,12 +15,10 @@ import co.electriccoin.zcash.preference.EncryptedPreferenceProvider import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.ui.common.datasource.AccountDataSource import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource -import co.electriccoin.zcash.ui.common.datasource.WalletSnapshotDataSource import co.electriccoin.zcash.ui.common.model.FastestServersState import co.electriccoin.zcash.ui.common.model.OnboardingState import co.electriccoin.zcash.ui.common.model.WalletAccount import co.electriccoin.zcash.ui.common.model.WalletRestoringState -import co.electriccoin.zcash.ui.common.model.WalletSnapshot import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider import co.electriccoin.zcash.ui.common.provider.PersistableWalletProvider import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider @@ -67,7 +65,6 @@ interface WalletRepository { val allAccounts: Flow?> val currentAccount: Flow - val currentWalletSnapshot: StateFlow /** * A flow of the wallet block synchronization state. @@ -105,7 +102,6 @@ class WalletRepositoryImpl( private val encryptedPreferenceProvider: EncryptedPreferenceProvider, private val restoreTimestampDataSource: RestoreTimestampDataSource, private val walletRestoringStateProvider: WalletRestoringStateProvider, - private val walletSnapshotDataSource: WalletSnapshotDataSource, ) : WalletRepository { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -185,8 +181,6 @@ class WalletRepositoryImpl( initialValue = FastestServersState(servers = emptyList(), isLoading = true) ) - override val currentWalletSnapshot: StateFlow = walletSnapshotDataSource.observe() - /** * A flow of the wallet block synchronization state. */ diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetCurrentTransactionsUseCase.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetTransactionsUseCase.kt similarity index 97% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetCurrentTransactionsUseCase.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetTransactionsUseCase.kt index 8eceb9e57..9119556ae 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetCurrentTransactionsUseCase.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/usecase/GetTransactionsUseCase.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.mapLatest -class GetCurrentTransactionsUseCase( +class GetTransactionsUseCase( private val transactionRepository: TransactionRepository, private val metadataRepository: MetadataRepository, ) { 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 066ad4340..aa0995959 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 @@ -11,7 +11,7 @@ import co.electriccoin.zcash.ui.common.model.WalletAccount import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState import co.electriccoin.zcash.ui.design.util.stringRes -import co.electriccoin.zcash.ui.screen.balances.action.BalanceAction +import co.electriccoin.zcash.ui.screen.balances.spendable.SpendableBalance import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.WhileSubscribed @@ -50,13 +50,7 @@ class BalanceWidgetViewModel( when { !args.isBalanceButtonEnabled -> null account == null -> null - account.totalBalance == account.spendableShieldedBalance -> null - account.totalBalance > account.spendableShieldedBalance && - account.spendableShieldedBalance == account.totalShieldedBalance && - account.totalTransparentBalance > Zatoshi(0) && - !account.isShieldingAvailable -> - null - + account.isAllShielded -> null account.totalBalance > account.spendableShieldedBalance && !account.isShieldedPending && account.totalShieldedBalance > Zatoshi(0) && @@ -94,7 +88,7 @@ class BalanceWidgetViewModel( showDust = args.showDust ) - private fun onBalanceButtonClick() = navigationRouter.forward(BalanceAction) + private fun onBalanceButtonClick() = navigationRouter.forward(SpendableBalance) } data class BalanceWidgetArgs( diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/AndroidBalanceAction.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/AndroidSpendableBalance.kt similarity index 66% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/AndroidBalanceAction.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/AndroidSpendableBalance.kt index 9321fbe31..5a6035227 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/AndroidBalanceAction.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/AndroidSpendableBalance.kt @@ -1,4 +1,4 @@ -package co.electriccoin.zcash.ui.screen.balances.action +package co.electriccoin.zcash.ui.screen.balances.spendable import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable @@ -9,11 +9,11 @@ import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AndroidBalanceAction() { - val vm = koinViewModel() +fun AndroidSpendableBalance() { + val vm = koinViewModel() val state by vm.state.collectAsStateWithLifecycle() - BalanceActionView(state) + SpendableBalanceView(state) } @Serializable -data object BalanceAction +data object SpendableBalance diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionState.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/SpendableBalanceState.kt similarity index 70% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionState.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/SpendableBalanceState.kt index ddbfc2713..7ea18d5bd 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionState.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/SpendableBalanceState.kt @@ -1,4 +1,4 @@ -package co.electriccoin.zcash.ui.screen.balances.action +package co.electriccoin.zcash.ui.screen.balances.spendable import androidx.compose.runtime.Immutable import cash.z.ecc.android.sdk.model.Zatoshi @@ -8,24 +8,24 @@ import co.electriccoin.zcash.ui.design.util.ImageResource import co.electriccoin.zcash.ui.design.util.StringResource @Immutable -data class BalanceActionState( +data class SpendableBalanceState( val title: StringResource, val message: StringResource, - val rows: List, - val shieldButton: BalanceShieldButtonState?, + val rows: List, + val shieldButton: SpendableBalanceShieldButtonState?, val positive: ButtonState, override val onBack: () -> Unit, ) : ModalBottomSheetState @Immutable -data class BalanceActionRowState( +data class SpendableBalanceRowState( val title: StringResource, val icon: ImageResource, val value: StringResource ) @Immutable -data class BalanceShieldButtonState( +data class SpendableBalanceShieldButtonState( val amount: Zatoshi, val onShieldClick: () -> Unit, ) 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/spendable/SpendableBalanceView.kt similarity index 92% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionView.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/SpendableBalanceView.kt index 81fbc8537..85c98db65 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/spendable/SpendableBalanceView.kt @@ -1,4 +1,4 @@ -package co.electriccoin.zcash.ui.screen.balances.action +package co.electriccoin.zcash.ui.screen.balances.spendable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -42,8 +42,8 @@ import co.electriccoin.zcash.ui.design.util.stringRes @OptIn(ExperimentalMaterial3Api::class) @Composable -fun BalanceActionView( - state: BalanceActionState?, +fun SpendableBalanceView( + state: SpendableBalanceState?, sheetState: SheetState = rememberScreenModalBottomSheetState(), ) { ZashiScreenModalBottomSheet( @@ -56,7 +56,7 @@ fun BalanceActionView( } @Composable -fun BottomSheetContent(state: BalanceActionState, modifier: Modifier = Modifier) { +fun BottomSheetContent(state: SpendableBalanceState, modifier: Modifier = Modifier) { Column( modifier = modifier @@ -97,7 +97,7 @@ fun BottomSheetContent(state: BalanceActionState, modifier: Modifier = Modifier) } @Composable -private fun BalanceActionRow(state: BalanceActionRowState) { +private fun BalanceActionRow(state: SpendableBalanceRowState) { Row( verticalAlignment = Alignment.CenterVertically ) { @@ -137,7 +137,7 @@ private fun BalanceActionRow(state: BalanceActionRowState) { } @Composable -private fun BalanceShieldButton(state: BalanceShieldButtonState) { +private fun BalanceShieldButton(state: SpendableBalanceShieldButtonState) { ZashiCard( modifier = Modifier.fillMaxWidth(), contentPadding = @@ -193,9 +193,9 @@ private fun BalanceShieldButton(state: BalanceShieldButtonState) { @Composable private fun Preview() = ZcashTheme { - BalanceActionView( + SpendableBalanceView( state = - BalanceActionState( + SpendableBalanceState( title = stringRes("Title"), message = stringRes("Subtitle"), positive = @@ -205,19 +205,19 @@ private fun Preview() = onBack = {}, rows = listOf( - BalanceActionRowState( + SpendableBalanceRowState( title = stringRes("Row"), icon = loadingImageRes(), value = stringRes("Value") ), - BalanceActionRowState( + SpendableBalanceRowState( title = stringRes("Row"), icon = imageRes(R.drawable.ic_balance_shield), value = stringRes("Value") ) ), shieldButton = - BalanceShieldButtonState( + SpendableBalanceShieldButtonState( amount = Zatoshi(10000), onShieldClick = {} ) 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/spendable/SpendableBalanceViewModel.kt similarity index 63% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/action/BalanceActionViewModel.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/spendable/SpendableBalanceViewModel.kt index 075d7efce..7d23ace12 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/spendable/SpendableBalanceViewModel.kt @@ -1,12 +1,17 @@ -package co.electriccoin.zcash.ui.screen.balances.action +package co.electriccoin.zcash.ui.screen.balances.spendable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import cash.z.ecc.sdk.extension.typicalFee import co.electriccoin.zcash.ui.NavigationRouter import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.common.datasource.AccountDataSource import co.electriccoin.zcash.ui.common.model.WalletAccount +import co.electriccoin.zcash.ui.common.repository.isPending +import co.electriccoin.zcash.ui.common.usecase.GetTransactionsUseCase +import co.electriccoin.zcash.ui.common.usecase.ListTransactionData import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.util.StringResource @@ -15,35 +20,41 @@ import co.electriccoin.zcash.ui.design.util.loadingImageRes import co.electriccoin.zcash.ui.design.util.stringRes import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.WhileSubscribed -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.stateIn -class BalanceActionViewModel( +class SpendableBalanceViewModel( accountDataSource: AccountDataSource, + getTransactions: GetTransactionsUseCase, private val navigationRouter: NavigationRouter, private val shieldFunds: ShieldFundsUseCase, ) : ViewModel() { val state = - accountDataSource.selectedAccount - .mapNotNull { - createState(it) - }.stateIn( + combine( + accountDataSource.selectedAccount, + getTransactions.observe() + ) { account, transactions -> + createState(account, transactions) + }.filterNotNull() + .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), initialValue = createState( - accountDataSource.allAccounts.value - .orEmpty() - .firstOrNull { it.isSelected } + account = + accountDataSource.allAccounts.value + .orEmpty() + .firstOrNull { it.isSelected }, + transactions = null ) ) - private fun createState(account: WalletAccount?): BalanceActionState? { + private fun createState(account: WalletAccount?, transactions: List?): SpendableBalanceState? { if (account == null) return null - - return BalanceActionState( + return SpendableBalanceState( title = stringRes(R.string.balance_action_title), - message = createMessage(account), + message = createMessage(account, transactions), positive = createPositiveButton(account), onBack = ::onBack, rows = createInfoRows(account), @@ -51,19 +62,26 @@ class BalanceActionViewModel( ) } - private fun createMessage(account: WalletAccount): StringResource { + private fun createMessage(account: WalletAccount, transactions: List?): StringResource { val pending = when { - account.totalShieldedBalance == account.spendableShieldedBalance -> - stringRes(R.string.balance_action_all_shielded) + account.isAllShielded -> stringRes(R.string.balance_action_all_shielded) - account.totalShieldedBalance > account.spendableShieldedBalance -> - stringRes(R.string.balance_action_pending) + account.totalBalance > account.spendableShieldedBalance -> + if (transactions.orEmpty().any { it.transaction.isPending }) { + stringRes(R.string.balance_action_pending) + } else { + stringRes(R.string.balance_action_syncing) + } else -> null } - val shielding = stringRes(R.string.balance_action_shield_message).takeIf { account.isShieldingAvailable } + val shielding = + stringRes( + R.string.balance_action_shield_message, + stringRes(Zatoshi.typicalFee) + ).takeIf { account.isShieldingAvailable } return if (pending != null && shielding != null) { pending + stringRes("\n\n") + shielding @@ -85,21 +103,21 @@ class BalanceActionViewModel( private fun createInfoRows(account: WalletAccount) = listOfNotNull( - BalanceActionRowState( + SpendableBalanceRowState( title = stringRes(R.string.balance_action_info_shielded), icon = imageRes(R.drawable.ic_balance_shield), value = stringRes(R.string.general_zec, stringRes(account.spendableShieldedBalance)) ), when { account.totalShieldedBalance > account.spendableShieldedBalance && account.isShieldedPending -> - BalanceActionRowState( + SpendableBalanceRowState( title = stringRes(R.string.balance_action_info_pending), icon = loadingImageRes(), value = stringRes(R.string.general_zec, stringRes(account.pendingShieldedBalance)) ) account.totalShieldedBalance > account.spendableShieldedBalance -> - BalanceActionRowState( + SpendableBalanceRowState( title = stringRes(R.string.balance_action_info_pending), icon = loadingImageRes(), value = @@ -113,8 +131,8 @@ class BalanceActionViewModel( }, ) - private fun createShieldButtonState(account: WalletAccount): BalanceShieldButtonState? = - BalanceShieldButtonState( + private fun createShieldButtonState(account: WalletAccount): SpendableBalanceShieldButtonState? = + SpendableBalanceShieldButtonState( amount = account.transparent.balance, onShieldClick = ::onShieldClick ).takeIf { account.isShieldingAvailable } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/AndroidWalletRestoringInfo.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/AndroidWalletRestoringInfo.kt index 71cd459ac..13f5d4624 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/AndroidWalletRestoringInfo.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/AndroidWalletRestoringInfo.kt @@ -7,11 +7,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import co.electriccoin.zcash.ui.NavigationRouter +import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.Spacer @@ -24,28 +25,28 @@ import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography +import co.electriccoin.zcash.ui.design.util.getValue import co.electriccoin.zcash.ui.design.util.stringRes import kotlinx.serialization.Serializable -import org.koin.compose.koinInject +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun AndroidWalletRestoringInfo() { - val navigationRouter = koinInject() - Content( - onBack = { navigationRouter.back() } - ) + val viewModel = koinViewModel() + val state by viewModel.state.collectAsStateWithLifecycle() + Content(state) } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun Content( - onBack: () -> Unit, + state: WalletRestoringInfoState, sheetState: SheetState = rememberScreenModalBottomSheetState(), ) { ZashiScreenModalBottomSheet( sheetState = sheetState, - onDismissRequest = onBack + onDismissRequest = state.onBack ) { Column( modifier = @@ -70,17 +71,17 @@ private fun Content( stringResource(R.string.home_info_restoring_bullet_2), color = ZashiColors.Text.textTertiary ) - Spacer(32.dp) - ZashiInfoText( - stringResource(R.string.home_info_restoring_note), - ) + state.info?.let { + Spacer(32.dp) + ZashiInfoText(it.getValue()) + } Spacer(24.dp) ZashiButton( modifier = Modifier.fillMaxWidth(), state = ButtonState( text = stringRes(R.string.general_ok), - onClick = onBack + onClick = state.onBack ) ) } @@ -93,7 +94,11 @@ private fun Content( private fun Preview() = ZcashTheme { Content( - onBack = {}, + state = + WalletRestoringInfoState( + onBack = {}, + info = stringRes(R.string.home_info_restoring_message) + ) ) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoState.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoState.kt new file mode 100644 index 000000000..70c25e53e --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoState.kt @@ -0,0 +1,11 @@ +package co.electriccoin.zcash.ui.screen.home.restoring + +import androidx.compose.runtime.Immutable +import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState +import co.electriccoin.zcash.ui.design.util.StringResource + +@Immutable +data class WalletRestoringInfoState( + val info: StringResource?, + override val onBack: () -> Unit +) : ModalBottomSheetState diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoViewModel.kt new file mode 100644 index 000000000..c2272ef1e --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/restoring/WalletRestoringInfoViewModel.kt @@ -0,0 +1,37 @@ +package co.electriccoin.zcash.ui.screen.home.restoring + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.ui.NavigationRouter +import co.electriccoin.zcash.ui.R +import co.electriccoin.zcash.ui.common.datasource.WalletSnapshotDataSource +import co.electriccoin.zcash.ui.common.model.WalletSnapshot +import co.electriccoin.zcash.ui.design.util.stringRes +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.WhileSubscribed +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class WalletRestoringInfoViewModel( + walletSnapshotDataSource: WalletSnapshotDataSource, + private val navigationRouter: NavigationRouter +) : ViewModel() { + val state = + walletSnapshotDataSource + .observe() + .map { createState(it) } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + initialValue = createState(walletSnapshotDataSource.observe().value) + ) + + private fun createState(snapshot: WalletSnapshot?) = + WalletRestoringInfoState( + onBack = ::onBack, + info = stringRes(R.string.home_info_restoring_note).takeIf { snapshot?.isSpendable == false } + ) + + private fun onBack() = navigationRouter.back() +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/shieldfunds/ShieldFundsInfoView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/shieldfunds/ShieldFundsInfoView.kt index 99ccd66f3..84a9d3ecc 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/shieldfunds/ShieldFundsInfoView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/shieldfunds/ShieldFundsInfoView.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.sdk.extension.typicalFee import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.Spacer @@ -72,7 +73,10 @@ private fun Content(state: ShieldFundsInfoState) { ) Spacer(24.dp) Text( - stringResource(R.string.home_info_transparent_message), + stringRes( + R.string.home_info_transparent_message, + stringRes(Zatoshi.typicalFee) + ).getValue(), color = ZashiColors.Text.textTertiary, style = ZashiTypography.textMd ) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveShielded.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveShielded.kt index 25916515d..6fc3f88ed 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveShielded.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveShielded.kt @@ -68,11 +68,12 @@ fun ReceiveShielded( modifier = Modifier.fillMaxWidth(), state = TransactionDetailInfoRowState( - title = if (state.isPending) { - stringRes(R.string.transaction_detail_info_transaction_status) - } else { - stringRes(R.string.transaction_detail_info_transaction_completed) - }, + title = + if (state.isPending) { + stringRes(R.string.transaction_detail_info_transaction_status) + } else { + stringRes(R.string.transaction_detail_info_transaction_completed) + }, message = state.completedTimestamp, shape = if (state.note != null) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveTransparent.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveTransparent.kt index b87e54754..c27a0e015 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveTransparent.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/ReceiveTransparent.kt @@ -54,11 +54,12 @@ fun ReceiveTransparent( modifier = Modifier.fillMaxWidth(), state = TransactionDetailInfoRowState( - title = if (state.isPending) { - stringRes(R.string.transaction_detail_info_transaction_status) - } else { - stringRes(R.string.transaction_detail_info_transaction_completed) - }, + title = + if (state.isPending) { + stringRes(R.string.transaction_detail_info_transaction_status) + } else { + stringRes(R.string.transaction_detail_info_transaction_completed) + }, message = state.completedTimestamp, shape = if (state.note != null) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendShielded.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendShielded.kt index 75af309df..00d977a36 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendShielded.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendShielded.kt @@ -118,11 +118,12 @@ fun SendShielded( modifier = Modifier.fillMaxWidth(), state = TransactionDetailInfoRowState( - title = if (state.isPending) { - stringRes(R.string.transaction_detail_info_transaction_status) - } else { - stringRes(R.string.transaction_detail_info_transaction_completed) - }, + title = + if (state.isPending) { + stringRes(R.string.transaction_detail_info_transaction_status) + } else { + stringRes(R.string.transaction_detail_info_transaction_completed) + }, message = state.completedTimestamp, shape = if (state.note == null) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendTransparent.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendTransparent.kt index 0c1ae1693..b40806b72 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendTransparent.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/SendTransparent.kt @@ -115,11 +115,12 @@ fun SendTransparent( modifier = Modifier.fillMaxWidth(), state = TransactionDetailInfoRowState( - title = if (state.isPending) { - stringRes(R.string.transaction_detail_info_transaction_status) - } else { - stringRes(R.string.transaction_detail_info_transaction_completed) - }, + title = + if (state.isPending) { + stringRes(R.string.transaction_detail_info_transaction_status) + } else { + stringRes(R.string.transaction_detail_info_transaction_completed) + }, message = state.completedTimestamp, shape = if (state.note != null) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/Shielding.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/Shielding.kt index 9b520c016..8676854ba 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/Shielding.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactiondetail/info/Shielding.kt @@ -54,11 +54,12 @@ fun Shielding( modifier = Modifier.fillMaxWidth(), state = TransactionDetailInfoRowState( - title = if (state.isPending) { - stringRes(R.string.transaction_detail_info_transaction_status) - } else { - stringRes(R.string.transaction_detail_info_transaction_completed) - }, + title = + if (state.isPending) { + stringRes(R.string.transaction_detail_info_transaction_status) + } else { + stringRes(R.string.transaction_detail_info_transaction_completed) + }, message = state.completedTimestamp, shape = TransactionDetailInfoShape.MIDDLE, ) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt index 007df9906..a3313b745 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/transactionhistory/widget/TransactionHistoryWidgetViewModel.kt @@ -10,7 +10,7 @@ import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource import co.electriccoin.zcash.ui.common.mapper.TransactionHistoryMapper import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.repository.Transaction -import co.electriccoin.zcash.ui.common.usecase.GetCurrentTransactionsUseCase +import co.electriccoin.zcash.ui.common.usecase.GetTransactionsUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.util.stringRes @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn class TransactionHistoryWidgetViewModel( - getCurrentTransactions: GetCurrentTransactionsUseCase, + getTransactions: GetTransactionsUseCase, getWalletRestoringState: GetWalletRestoringStateUseCase, private val transactionHistoryMapper: TransactionHistoryMapper, private val navigationRouter: NavigationRouter, @@ -31,7 +31,7 @@ class TransactionHistoryWidgetViewModel( ) : ViewModel() { val state = combine( - getCurrentTransactions.observe(), + getTransactions.observe(), getWalletRestoringState.observe(), ) { transactions, restoringState -> when { 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 0aebe297e..fa8f1d571 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 @@ -43,7 +43,7 @@ To protect user privacy, Zashi doesn\'t support spending transparent ZEC. Tap the \"Shield\" button below to make your transparent funds spendable and your total Zashi balance private. This will create a shielding in-wallet transaction, consolidating your - transparent and shielded funds. (Typical fee: .001 ZEC) + transparent and shielded funds. (Typical fee: %s ZEC) Transparent Wallet Disconnected You appear to be offline. Please restore your internet connection @@ -74,7 +74,8 @@ 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. Doing so will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC) - Shielded ZEC (Spendable) + Zashi is scanning the blockchain to make sure your balances are displayed correctly. + Shield your transparent ZEC to make it spendable and private. Doing so will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: %s ZEC) + Shielded (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 0aebe297e..fa8f1d571 100644 --- a/ui-lib/src/main/res/ui/home/values/strings.xml +++ b/ui-lib/src/main/res/ui/home/values/strings.xml @@ -43,7 +43,7 @@ To protect user privacy, Zashi doesn\'t support spending transparent ZEC. Tap the \"Shield\" button below to make your transparent funds spendable and your total Zashi balance private. This will create a shielding in-wallet transaction, consolidating your - transparent and shielded funds. (Typical fee: .001 ZEC) + transparent and shielded funds. (Typical fee: %s ZEC) Transparent Wallet Disconnected You appear to be offline. Please restore your internet connection @@ -74,7 +74,8 @@ 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. Doing so will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC) - Shielded ZEC (Spendable) + Zashi is scanning the blockchain to make sure your balances are displayed correctly. + Shield your transparent ZEC to make it spendable and private. Doing so will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: %s ZEC) + Shielded (Spendable) Pending \ No newline at end of file