Message persistence

This commit is contained in:
Milan Cerovsky 2025-04-10 20:38:39 +02:00
parent d6d500eaed
commit c45d672995
42 changed files with 685 additions and 395 deletions

View File

@ -203,7 +203,7 @@ object ZashiButtonDefaults {
contentColor: Color = source.Btns.Secondary.btnSecondaryFg, contentColor: Color = source.Btns.Secondary.btnSecondaryFg,
borderColor: Color = Color.Unspecified, borderColor: Color = Color.Unspecified,
disabledContainerColor: Color = source.Btns.Secondary.btnSecondaryBgDisabled, disabledContainerColor: Color = source.Btns.Secondary.btnSecondaryBgDisabled,
disabledContentColor: Color = source.Btns.Secondary.btnSecondaryFg, disabledContentColor: Color = source.Btns.Secondary.btnSecondaryFgDisabled,
) = ZashiButtonColors( ) = ZashiButtonColors(
containerColor = containerColor, containerColor = containerColor,
contentColor = contentColor, contentColor = contentColor,

View File

@ -2,6 +2,7 @@ package co.electriccoin.zcash.ui.design.component
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsBottomHeight
@ -27,7 +28,7 @@ import co.electriccoin.zcash.ui.design.LocalSheetStateManager
fun <T : ModalBottomSheetState> ZashiScreenModalBottomSheet( fun <T : ModalBottomSheetState> ZashiScreenModalBottomSheet(
state: T?, state: T?,
sheetState: SheetState = rememberScreenModalBottomSheetState(), sheetState: SheetState = rememberScreenModalBottomSheetState(),
content: @Composable (state: T) -> Unit = {}, content: @Composable ColumnScope.(state: T) -> Unit = {},
) { ) {
val parent = LocalView.current.parent val parent = LocalView.current.parent
SideEffect { SideEffect {

View File

@ -2,10 +2,14 @@ package co.electriccoin.zcash.di
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.datasource.AccountDataSourceImpl import co.electriccoin.zcash.ui.common.datasource.AccountDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.MessageAvailabilityDataSource
import co.electriccoin.zcash.ui.common.datasource.MessageAvailabilityDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.ProposalDataSource import co.electriccoin.zcash.ui.common.datasource.ProposalDataSource
import co.electriccoin.zcash.ui.common.datasource.ProposalDataSourceImpl import co.electriccoin.zcash.ui.common.datasource.ProposalDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSourceImpl import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.ShieldFundsDataSource
import co.electriccoin.zcash.ui.common.datasource.ShieldFundsDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSourceImpl import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.ZashiSpendingKeyDataSource import co.electriccoin.zcash.ui.common.datasource.ZashiSpendingKeyDataSource
@ -21,4 +25,6 @@ val dataSourceModule =
singleOf(::ProposalDataSourceImpl) bind ProposalDataSource::class singleOf(::ProposalDataSourceImpl) bind ProposalDataSource::class
singleOf(::RestoreTimestampDataSourceImpl) bind RestoreTimestampDataSource::class singleOf(::RestoreTimestampDataSourceImpl) bind RestoreTimestampDataSource::class
singleOf(::WalletBackupDataSourceImpl) bind WalletBackupDataSource::class singleOf(::WalletBackupDataSourceImpl) bind WalletBackupDataSource::class
singleOf(::ShieldFundsDataSourceImpl) bind ShieldFundsDataSource::class
singleOf(::MessageAvailabilityDataSourceImpl) bind MessageAvailabilityDataSource::class
} }

View File

@ -18,6 +18,8 @@ import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeTimestampStor
import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeTimestampStorageProviderImpl import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeTimestampStorageProviderImpl
import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider
import co.electriccoin.zcash.ui.common.provider.SynchronizerProviderImpl import co.electriccoin.zcash.ui.common.provider.SynchronizerProviderImpl
import co.electriccoin.zcash.ui.common.provider.WalletBackupConsentStorageProvider
import co.electriccoin.zcash.ui.common.provider.WalletBackupConsentStorageProviderImpl
import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProvider import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProvider
import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProviderImpl import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProviderImpl
import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeCountStorageProvider import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeCountStorageProvider
@ -49,4 +51,5 @@ val providerModule =
factoryOf(::WalletBackupRemindMeTimestampStorageProviderImpl) bind factoryOf(::WalletBackupRemindMeTimestampStorageProviderImpl) bind
WalletBackupRemindMeTimestampStorageProvider::class WalletBackupRemindMeTimestampStorageProvider::class
factoryOf(::WalletBackupFlagStorageProviderImpl) bind WalletBackupFlagStorageProvider::class factoryOf(::WalletBackupFlagStorageProviderImpl) bind WalletBackupFlagStorageProvider::class
factoryOf(::WalletBackupConsentStorageProviderImpl) bind WalletBackupConsentStorageProvider::class
} }

View File

@ -10,6 +10,8 @@ import co.electriccoin.zcash.ui.common.repository.FlexaRepository
import co.electriccoin.zcash.ui.common.repository.FlexaRepositoryImpl import co.electriccoin.zcash.ui.common.repository.FlexaRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepository import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepository
import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepositoryImpl import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepository import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepository
import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepositoryImpl import co.electriccoin.zcash.ui.common.repository.TransactionFilterRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.TransactionRepository import co.electriccoin.zcash.ui.common.repository.TransactionRepository
@ -33,4 +35,5 @@ val repositoryModule =
singleOf(::TransactionRepositoryImpl) bind TransactionRepository::class singleOf(::TransactionRepositoryImpl) bind TransactionRepository::class
singleOf(::TransactionFilterRepositoryImpl) bind TransactionFilterRepository::class singleOf(::TransactionFilterRepositoryImpl) bind TransactionFilterRepository::class
singleOf(::ZashiProposalRepositoryImpl) bind ZashiProposalRepository::class singleOf(::ZashiProposalRepositoryImpl) bind ZashiProposalRepository::class
singleOf(::ShieldFundsRepositoryImpl) bind ShieldFundsRepository::class
} }

View File

@ -8,7 +8,6 @@ import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateFlexaTransactionUseCase import co.electriccoin.zcash.ui.common.usecase.CreateFlexaTransactionUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateKeystoneAccountUseCase import co.electriccoin.zcash.ui.common.usecase.CreateKeystoneAccountUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateKeystoneProposalPCZTEncoderUseCase import co.electriccoin.zcash.ui.common.usecase.CreateKeystoneProposalPCZTEncoderUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateKeystoneShieldProposalUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateOrUpdateTransactionNoteUseCase import co.electriccoin.zcash.ui.common.usecase.CreateOrUpdateTransactionNoteUseCase
import co.electriccoin.zcash.ui.common.usecase.CreateProposalUseCase import co.electriccoin.zcash.ui.common.usecase.CreateProposalUseCase
import co.electriccoin.zcash.ui.common.usecase.DeleteContactUseCase import co.electriccoin.zcash.ui.common.usecase.DeleteContactUseCase
@ -39,21 +38,19 @@ import co.electriccoin.zcash.ui.common.usecase.GetTransparentAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.GetWalletAccountsUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletAccountsUseCase
import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase
import co.electriccoin.zcash.ui.common.usecase.GetZashiAccountUseCase import co.electriccoin.zcash.ui.common.usecase.GetZashiAccountUseCase
import co.electriccoin.zcash.ui.common.usecase.GetZashiSpendingKeyUseCase
import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase import co.electriccoin.zcash.ui.common.usecase.IsCoinbaseAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase import co.electriccoin.zcash.ui.common.usecase.IsFlexaAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToWalletBackupUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToWalletBackupUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveClearSendUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveClearSendUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveContactPickedUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveContactPickedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveOnAccountChangedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveProposalUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveProposalUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedEndpointUseCase
@ -70,6 +67,7 @@ import co.electriccoin.zcash.ui.common.usecase.ParseKeystoneUrToZashiAccountsUse
import co.electriccoin.zcash.ui.common.usecase.PersistEndpointUseCase import co.electriccoin.zcash.ui.common.usecase.PersistEndpointUseCase
import co.electriccoin.zcash.ui.common.usecase.PrefillSendUseCase import co.electriccoin.zcash.ui.common.usecase.PrefillSendUseCase
import co.electriccoin.zcash.ui.common.usecase.RefreshFastestServersUseCase import co.electriccoin.zcash.ui.common.usecase.RefreshFastestServersUseCase
import co.electriccoin.zcash.ui.common.usecase.RemindShieldFundsLaterUseCase
import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase
import co.electriccoin.zcash.ui.common.usecase.RescanBlockchainUseCase import co.electriccoin.zcash.ui.common.usecase.RescanBlockchainUseCase
import co.electriccoin.zcash.ui.common.usecase.ResetInMemoryDataUseCase import co.electriccoin.zcash.ui.common.usecase.ResetInMemoryDataUseCase
@ -83,6 +81,7 @@ import co.electriccoin.zcash.ui.common.usecase.SendSupportEmailUseCase
import co.electriccoin.zcash.ui.common.usecase.SendTransactionAgainUseCase import co.electriccoin.zcash.ui.common.usecase.SendTransactionAgainUseCase
import co.electriccoin.zcash.ui.common.usecase.ShareImageUseCase import co.electriccoin.zcash.ui.common.usecase.ShareImageUseCase
import co.electriccoin.zcash.ui.common.usecase.SharePCZTUseCase import co.electriccoin.zcash.ui.common.usecase.SharePCZTUseCase
import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase
import co.electriccoin.zcash.ui.common.usecase.UpdateContactUseCase import co.electriccoin.zcash.ui.common.usecase.UpdateContactUseCase
import co.electriccoin.zcash.ui.common.usecase.ValidateContactAddressUseCase import co.electriccoin.zcash.ui.common.usecase.ValidateContactAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.ValidateContactNameUseCase import co.electriccoin.zcash.ui.common.usecase.ValidateContactNameUseCase
@ -125,7 +124,6 @@ val useCaseModule =
factoryOf(::Zip321BuildUriUseCase) factoryOf(::Zip321BuildUriUseCase)
factoryOf(::Zip321ParseUriValidationUseCase) factoryOf(::Zip321ParseUriValidationUseCase)
factoryOf(::IsCoinbaseAvailableUseCase) factoryOf(::IsCoinbaseAvailableUseCase)
factoryOf(::GetZashiSpendingKeyUseCase)
factoryOf(::ObservePersistableWalletUseCase) factoryOf(::ObservePersistableWalletUseCase)
factoryOf(::GetSupportUseCase) factoryOf(::GetSupportUseCase)
factoryOf(::SendEmailUseCase) factoryOf(::SendEmailUseCase)
@ -148,14 +146,12 @@ val useCaseModule =
factoryOf(::CreateProposalUseCase) factoryOf(::CreateProposalUseCase)
factoryOf(::OnZip321ScannedUseCase) factoryOf(::OnZip321ScannedUseCase)
factoryOf(::OnAddressScannedUseCase) factoryOf(::OnAddressScannedUseCase)
factoryOf(::CreateKeystoneShieldProposalUseCase)
factoryOf(::ParseKeystonePCZTUseCase) factoryOf(::ParseKeystonePCZTUseCase)
factoryOf(::ParseKeystoneSignInRequestUseCase) factoryOf(::ParseKeystoneSignInRequestUseCase)
factoryOf(::CancelProposalFlowUseCase) factoryOf(::CancelProposalFlowUseCase)
factoryOf(::ObserveProposalUseCase) factoryOf(::ObserveProposalUseCase)
factoryOf(::SharePCZTUseCase) factoryOf(::SharePCZTUseCase)
factoryOf(::CreateKeystoneProposalPCZTEncoderUseCase) factoryOf(::CreateKeystoneProposalPCZTEncoderUseCase)
factoryOf(::ObserveOnAccountChangedUseCase)
factoryOf(::ViewTransactionsAfterSuccessfulProposalUseCase) factoryOf(::ViewTransactionsAfterSuccessfulProposalUseCase)
factoryOf(::ViewTransactionDetailAfterSuccessfulProposalUseCase) factoryOf(::ViewTransactionDetailAfterSuccessfulProposalUseCase)
factoryOf(::NavigateToCoinbaseUseCase) factoryOf(::NavigateToCoinbaseUseCase)
@ -192,4 +188,6 @@ val useCaseModule =
factoryOf(::GetHomeMessageUseCase) factoryOf(::GetHomeMessageUseCase)
factoryOf(::OnUserSavedWalletBackupUseCase) factoryOf(::OnUserSavedWalletBackupUseCase)
factoryOf(::RemindWalletBackupLaterUseCase) factoryOf(::RemindWalletBackupLaterUseCase)
factoryOf(::RemindShieldFundsLaterUseCase)
singleOf(::ShieldFundsUseCase)
} }

View File

@ -19,7 +19,7 @@ import co.electriccoin.zcash.ui.screen.flexa.FlexaViewModel
import co.electriccoin.zcash.ui.screen.home.HomeViewModel 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.WalletBackupDetailViewModel
import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupInfoViewModel import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupInfoViewModel
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceInfoViewModel import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfoViewModel
import co.electriccoin.zcash.ui.screen.integrations.IntegrationsViewModel import co.electriccoin.zcash.ui.screen.integrations.IntegrationsViewModel
import co.electriccoin.zcash.ui.screen.qrcode.viewmodel.QrCodeViewModel import co.electriccoin.zcash.ui.screen.qrcode.viewmodel.QrCodeViewModel
import co.electriccoin.zcash.ui.screen.receive.viewmodel.ReceiveViewModel import co.electriccoin.zcash.ui.screen.receive.viewmodel.ReceiveViewModel
@ -38,7 +38,6 @@ import co.electriccoin.zcash.ui.screen.walletbackup.WalletBackupViewModel
import co.electriccoin.zcash.ui.screen.selectkeystoneaccount.SelectKeystoneAccount import co.electriccoin.zcash.ui.screen.selectkeystoneaccount.SelectKeystoneAccount
import co.electriccoin.zcash.ui.screen.selectkeystoneaccount.viewmodel.SelectKeystoneAccountViewModel import co.electriccoin.zcash.ui.screen.selectkeystoneaccount.viewmodel.SelectKeystoneAccountViewModel
import co.electriccoin.zcash.ui.screen.send.SendViewModel import co.electriccoin.zcash.ui.screen.send.SendViewModel
import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel
import co.electriccoin.zcash.ui.screen.settings.viewmodel.ScreenBrightnessViewModel import co.electriccoin.zcash.ui.screen.settings.viewmodel.ScreenBrightnessViewModel
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.viewmodel.SignKeystoneTransactionViewModel import co.electriccoin.zcash.ui.screen.signkeystonetransaction.viewmodel.SignKeystoneTransactionViewModel
@ -69,7 +68,6 @@ val viewModelModule =
viewModelOf(::SettingsViewModel) viewModelOf(::SettingsViewModel)
viewModelOf(::AdvancedSettingsViewModel) viewModelOf(::AdvancedSettingsViewModel)
viewModelOf(::SupportViewModel) viewModelOf(::SupportViewModel)
viewModelOf(::CreateTransactionsViewModel)
viewModelOf(::RestoreSuccessViewModel) viewModelOf(::RestoreSuccessViewModel)
viewModelOf(::WhatsNewViewModel) viewModelOf(::WhatsNewViewModel)
viewModelOf(::ChooseServerViewModel) viewModelOf(::ChooseServerViewModel)
@ -147,7 +145,7 @@ val viewModelModule =
viewModelOf(::RestoreBDHeightViewModel) viewModelOf(::RestoreBDHeightViewModel)
viewModelOf(::RestoreBDDateViewModel) viewModelOf(::RestoreBDDateViewModel)
viewModelOf(::RestoreBDEstimationViewModel) viewModelOf(::RestoreBDEstimationViewModel)
viewModelOf(::TransparentBalanceInfoViewModel) viewModelOf(::ShieldFundsInfoViewModel)
viewModelOf(::WalletBackupInfoViewModel) viewModelOf(::WalletBackupInfoViewModel)
viewModelOf(::ExchangeRateOptInViewModel) viewModelOf(::ExchangeRateOptInViewModel)
viewModelOf(::ExchangeRateSettingsViewModel) viewModelOf(::ExchangeRateSettingsViewModel)

View File

@ -78,8 +78,8 @@ import co.electriccoin.zcash.ui.screen.home.disconnected.WalletDisconnectedInfo
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfo import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfo
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingInfo import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingInfo
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingInfo import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingInfo
import co.electriccoin.zcash.ui.screen.home.transparentbalance.AndroidTransparentBalanceInfo import co.electriccoin.zcash.ui.screen.home.shieldfunds.AndroidShieldFundsInfo
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceInfo import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo
import co.electriccoin.zcash.ui.screen.integrations.AndroidDialogIntegrations import co.electriccoin.zcash.ui.screen.integrations.AndroidDialogIntegrations
import co.electriccoin.zcash.ui.screen.integrations.AndroidIntegrations import co.electriccoin.zcash.ui.screen.integrations.AndroidIntegrations
import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations
@ -422,14 +422,14 @@ internal fun MainActivity.Navigation() {
) { ) {
AndroidWalletBackupInfo() AndroidWalletBackupInfo()
} }
dialog<TransparentBalanceInfo>( dialog<ShieldFundsInfo>(
dialogProperties = dialogProperties =
DialogProperties( DialogProperties(
dismissOnBackPress = false, dismissOnBackPress = false,
dismissOnClickOutside = false dismissOnClickOutside = false
) )
) { ) {
AndroidTransparentBalanceInfo() AndroidShieldFundsInfo()
} }
dialog<WalletDisconnectedInfo>( dialog<WalletDisconnectedInfo>(
dialogProperties = dialogProperties =

View File

@ -24,7 +24,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -37,13 +36,10 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
interface AccountDataSource { interface AccountDataSource {
val onAccountChanged: Flow<Unit>
val allAccounts: StateFlow<List<WalletAccount>?> val allAccounts: StateFlow<List<WalletAccount>?>
val selectedAccount: Flow<WalletAccount?> val selectedAccount: Flow<WalletAccount?>
@ -74,8 +70,6 @@ class AccountDataSourceImpl(
) : AccountDataSource { ) : AccountDataSource {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override val onAccountChanged = MutableSharedFlow<Unit>()
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val internalAccounts: Flow<List<InternalAccountWithBalances>?> = private val internalAccounts: Flow<List<InternalAccountWithBalances>?> =
synchronizerProvider synchronizerProvider
@ -224,15 +218,7 @@ class AccountDataSourceImpl(
override suspend fun selectAccount(account: Account) { override suspend fun selectAccount(account: Account) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val current = selectedAccountUUIDProvider.getUUID()
selectedAccountUUIDProvider.setUUID(account.accountUuid) selectedAccountUUIDProvider.setUUID(account.accountUuid)
scope.launch {
if (current != account.accountUuid) {
onAccountChanged.emit(Unit)
}
}
} }
} }

View File

@ -0,0 +1,46 @@
package co.electriccoin.zcash.ui.common.datasource
import androidx.lifecycle.Lifecycle
import co.electriccoin.zcash.ui.common.provider.ApplicationStateProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
interface MessageAvailabilityDataSource {
val canShowMessage: Boolean
fun observe(): StateFlow<Boolean>
fun onMessageShown()
}
class MessageAvailabilityDataSourceImpl(
private val applicationStateProvider: ApplicationStateProvider
): MessageAvailabilityDataSource {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val state = MutableStateFlow(true)
override val canShowMessage: Boolean
get() = state.value
init {
scope.launch {
applicationStateProvider.state.collect {
if (it == Lifecycle.Event.ON_START) {
state.update { true }
}
}
}
}
override fun observe(): StateFlow<Boolean> = state.asStateFlow()
override fun onMessageShown() {
state.update { false }
}
}

View File

@ -0,0 +1,93 @@
package co.electriccoin.zcash.ui.common.datasource
import androidx.annotation.StringRes
import cash.z.ecc.android.sdk.model.AccountUuid
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeCountStorageProvider
import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeTimestampStorageProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import java.time.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface ShieldFundsDataSource {
suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability>
suspend fun remindMeLater(forAccount: AccountUuid)
}
class ShieldFundsDataSourceImpl(
private val shieldFundsRemindMeCountStorageProvider: ShieldFundsRemindMeCountStorageProvider,
private val shieldFundsRemindMeTimestampStorageProvider: ShieldFundsRemindMeTimestampStorageProvider
): ShieldFundsDataSource {
@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> = combine(
shieldFundsRemindMeCountStorageProvider.observe(forAccount),
shieldFundsRemindMeTimestampStorageProvider.observe(forAccount)
) { count, timestamp ->
count to timestamp
}.flatMapLatest {(count, timestamp) ->
when {
timestamp == null -> flowOf(ShieldFundsAvailability.Available(ShieldFundsLockoutDuration.TWO_DAYS))
count == 1 -> calculateNext(
lastTimestamp = timestamp,
lastLockoutDuration = ShieldFundsLockoutDuration.TWO_DAYS,
nextLockoutDuration = ShieldFundsLockoutDuration.TWO_WEEKS
)
else -> calculateNext(
lastTimestamp = timestamp,
lastLockoutDuration = if (count == 2) {
ShieldFundsLockoutDuration.TWO_WEEKS
} else {
ShieldFundsLockoutDuration.ONE_MONTH
},
nextLockoutDuration = ShieldFundsLockoutDuration.ONE_MONTH
)
}
}
override suspend fun remindMeLater(forAccount: AccountUuid) {
val count = shieldFundsRemindMeCountStorageProvider.get(forAccount)
val timestamp = Instant.now()
shieldFundsRemindMeCountStorageProvider.store(forAccount, count + 1)
shieldFundsRemindMeTimestampStorageProvider.store(forAccount, timestamp)
}
private fun calculateNext(
lastTimestamp: Instant,
lastLockoutDuration: ShieldFundsLockoutDuration,
nextLockoutDuration: ShieldFundsLockoutDuration
): Flow<ShieldFundsAvailability> {
val nextAvailableTimestamp = lastTimestamp.plusMillis(lastLockoutDuration.duration.inWholeMilliseconds)
val now = Instant.now()
return if (nextAvailableTimestamp > now) {
flow {
val remaining = nextAvailableTimestamp.toEpochMilli() - now.toEpochMilli()
emit(ShieldFundsAvailability.Unavailable)
delay(remaining)
emit(ShieldFundsAvailability.Available(nextLockoutDuration))
}
} else {
flowOf(ShieldFundsAvailability.Available(nextLockoutDuration))
}
}
}
sealed interface ShieldFundsAvailability {
data class Available(val lockoutDuration: ShieldFundsLockoutDuration) : ShieldFundsAvailability
data object Unavailable : ShieldFundsAvailability
}
enum class ShieldFundsLockoutDuration(val duration: Duration, @StringRes val res: Int) {
TWO_DAYS(10.seconds, R.string.general_remind_me_in_two_days),
TWO_WEEKS(20.seconds, R.string.general_remind_me_in_two_weeks),
ONE_MONTH(30.seconds, R.string.general_remind_me_in_two_months)
}

View File

@ -1,35 +0,0 @@
package co.electriccoin.zcash.ui.common.datasource
import cash.z.ecc.android.sdk.model.AccountUuid
import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeCountStorageProvider
import co.electriccoin.zcash.ui.common.provider.ShieldFundsRemindMeTimestampStorageProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
interface ShieldFundsRemindMeDataSource {
suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability>
}
class ShieldFundsRemindMeDataSourceImpl(
private val shieldFundsRemindMeCountStorageProvider: ShieldFundsRemindMeCountStorageProvider,
private val shieldFundsRemindMeTimestampStorageProvider: ShieldFundsRemindMeTimestampStorageProvider
): ShieldFundsRemindMeDataSource {
override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> = combine(
shieldFundsRemindMeCountStorageProvider.observe(forAccount),
shieldFundsRemindMeTimestampStorageProvider.observe(forAccount)
) { count, timestamp ->
when {
timestamp == null -> ShieldFundsAvailability.Available(1.days)
count == 1 -> ShieldFundsAvailability.Available(2.days)
count == 2 -> ShieldFundsAvailability.Available(3.days)
else -> ShieldFundsAvailability.Unavailable
}
}
}
sealed interface ShieldFundsAvailability {
data class Available(val nextLockoutDuration: Duration) : ShieldFundsAvailability
data object Unavailable : ShieldFundsAvailability
}

View File

@ -1,5 +1,7 @@
package co.electriccoin.zcash.ui.common.datasource package co.electriccoin.zcash.ui.common.datasource
import androidx.annotation.StringRes
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProvider import co.electriccoin.zcash.ui.common.provider.WalletBackupFlagStorageProvider
import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeCountStorageProvider import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeCountStorageProvider
import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeTimestampStorageProvider import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeTimestampStorageProvider
@ -38,21 +40,21 @@ class WalletBackupDataSourceImpl(
}.flatMapLatest { (isBackedUp, count, timestamp) -> }.flatMapLatest { (isBackedUp, count, timestamp) ->
when { when {
isBackedUp -> flowOf(WalletBackupAvailability.Unavailable) isBackedUp -> flowOf(WalletBackupAvailability.Unavailable)
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.ONE_DAY)) timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS))
count == 1 -> calculateNext( count == 1 -> calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = WalletBackupLockoutDuration.ONE_DAY, lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS,
nextLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS
) )
else -> calculateNext( else -> calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = if (count == 2) { lastLockoutDuration = if (count == 2) {
WalletBackupLockoutDuration.TWO_DAYS WalletBackupLockoutDuration.TWO_WEEKS
} else { } else {
WalletBackupLockoutDuration.THREE_DAYS WalletBackupLockoutDuration.ONE_MONTH
}, },
nextLockoutDuration = WalletBackupLockoutDuration.THREE_DAYS nextLockoutDuration = WalletBackupLockoutDuration.ONE_MONTH
) )
} }
} }
@ -93,8 +95,8 @@ sealed interface WalletBackupAvailability {
data object Unavailable : WalletBackupAvailability data object Unavailable : WalletBackupAvailability
} }
enum class WalletBackupLockoutDuration(val duration: Duration) { enum class WalletBackupLockoutDuration(val duration: Duration, @StringRes val res: Int) {
ONE_DAY(10.seconds), TWO_DAYS(10.seconds, R.string.general_remind_me_in_two_days),
TWO_DAYS(20.seconds), TWO_WEEKS(20.seconds, R.string.general_remind_me_in_two_weeks),
THREE_DAYS(30.seconds) ONE_MONTH(30.seconds, R.string.general_remind_me_in_two_months),
} }

View File

@ -0,0 +1,11 @@
package co.electriccoin.zcash.ui.common.provider
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
interface WalletBackupConsentStorageProvider : BooleanStorageProvider
class WalletBackupConsentStorageProviderImpl(
override val preferenceHolder: EncryptedPreferenceProvider
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_consent")),
WalletBackupConsentStorageProvider

View File

@ -0,0 +1,65 @@
package co.electriccoin.zcash.ui.common.repository
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.datasource.ShieldFundsAvailability
import co.electriccoin.zcash.ui.common.datasource.ShieldFundsDataSource
import co.electriccoin.zcash.ui.common.datasource.ShieldFundsLockoutDuration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
interface ShieldFundsRepository {
val availability: Flow<ShieldFundsData>
suspend fun remindMeLater()
}
class ShieldFundsRepositoryImpl(
private val accountDataSource: AccountDataSource,
private val shieldFundsDataSource: ShieldFundsDataSource,
) : ShieldFundsRepository {
@OptIn(ExperimentalCoroutinesApi::class)
override val availability: Flow<ShieldFundsData> = accountDataSource
.selectedAccount
.flatMapLatest { account ->
when {
account == null ->
flowOf(ShieldFundsData.Unavailable)
account.transparent.balance >= Zatoshi(DEFAULT_SHIELDING_THRESHOLD) ->
shieldFundsDataSource.observe(account.sdkAccount.accountUuid).map {
when (it) {
is ShieldFundsAvailability.Available -> ShieldFundsData.Available(
lockoutDuration = it.lockoutDuration,
amount = account.transparent.balance
)
ShieldFundsAvailability.Unavailable -> ShieldFundsData.Unavailable
}
}
else -> flowOf(ShieldFundsData.Unavailable)
}
}
override suspend fun remindMeLater() {
shieldFundsDataSource.remindMeLater(
forAccount = accountDataSource.getSelectedAccount().sdkAccount.accountUuid
)
}
}
sealed interface ShieldFundsData {
data class Available(
val lockoutDuration: ShieldFundsLockoutDuration,
val amount: Zatoshi
) : ShieldFundsData
data object Unavailable : ShieldFundsData
}
private const val DEFAULT_SHIELDING_THRESHOLD = 100000L

View File

@ -13,6 +13,7 @@ import cash.z.ecc.android.sdk.model.proposeSend
import cash.z.ecc.android.sdk.type.AddressType import cash.z.ecc.android.sdk.type.AddressType
import co.electriccoin.zcash.spackle.Twig import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.ZashiSpendingKeyDataSource
import co.electriccoin.zcash.ui.common.model.SubmitResult import co.electriccoin.zcash.ui.common.model.SubmitResult
import co.electriccoin.zcash.ui.common.repository.BiometricRepository import co.electriccoin.zcash.ui.common.repository.BiometricRepository
import co.electriccoin.zcash.ui.common.repository.BiometricRequest import co.electriccoin.zcash.ui.common.repository.BiometricRequest
@ -25,7 +26,7 @@ import com.flexa.spend.buildSpend
class CreateFlexaTransactionUseCase( class CreateFlexaTransactionUseCase(
private val getSynchronizer: GetSynchronizerUseCase, private val getSynchronizer: GetSynchronizerUseCase,
private val getZashiAccount: GetZashiAccountUseCase, private val getZashiAccount: GetZashiAccountUseCase,
private val getSpendingKey: GetZashiSpendingKeyUseCase, private val zashiSpendingKeyDataSource: ZashiSpendingKeyDataSource,
private val biometricRepository: BiometricRepository, private val biometricRepository: BiometricRepository,
private val context: Context, private val context: Context,
) { ) {
@ -42,7 +43,10 @@ class CreateFlexaTransactionUseCase(
) )
}.onSuccess { proposal -> }.onSuccess { proposal ->
Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" } Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
val result = submitTransactions(proposal = proposal, spendingKey = getSpendingKey()) val result = submitTransactions(
proposal = proposal,
spendingKey = zashiSpendingKeyDataSource.getZashiSpendingKey()
)
when (val output = result.first) { when (val output = result.first) {
is SubmitResult.Success -> { is SubmitResult.Success -> {
Twig.debug { "Transaction successful $result" } Twig.debug { "Transaction successful $result" }

View File

@ -1,22 +0,0 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepository
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.SignKeystoneTransaction
class CreateKeystoneShieldProposalUseCase(
private val keystoneProposalRepository: KeystoneProposalRepository,
private val navigationRouter: NavigationRouter
) {
@Suppress("TooGenericExceptionCaught")
suspend operator fun invoke() {
try {
keystoneProposalRepository.createShieldProposal()
keystoneProposalRepository.createPCZTFromProposal()
navigationRouter.forward(SignKeystoneTransaction)
} catch (e: Exception) {
keystoneProposalRepository.clear()
throw e
}
}
}

View File

@ -6,19 +6,17 @@ import co.electriccoin.zcash.ui.common.datasource.WalletBackupAvailability
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
import co.electriccoin.zcash.ui.common.repository.ShieldFundsData
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
import co.electriccoin.zcash.ui.common.repository.WalletRepository import co.electriccoin.zcash.ui.common.repository.WalletRepository
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
import co.electriccoin.zcash.ui.util.Quadruple
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -26,23 +24,23 @@ class GetHomeMessageUseCase(
private val walletRepository: WalletRepository, private val walletRepository: WalletRepository,
private val walletBackupDataSource: WalletBackupDataSource, private val walletBackupDataSource: WalletBackupDataSource,
private val exchangeRateRepository: ExchangeRateRepository, private val exchangeRateRepository: ExchangeRateRepository,
private val shieldFundsRepository: ShieldFundsRepository,
) { ) {
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @OptIn(FlowPreview::class)
fun observe() = combine( fun observe(): Flow<HomeMessageData?> = combine(
walletRepository.currentWalletSnapshot.filterNotNull(), walletRepository.currentWalletSnapshot.filterNotNull(),
walletRepository.walletRestoringState, walletRepository.walletRestoringState,
walletBackupDataSource.observe(), walletBackupDataSource.observe(),
exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(), exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(),
) { walletSnapshot, walletStateInformation, backup, isCCAvailable -> shieldFundsRepository.availability
Quadruple(walletSnapshot, walletStateInformation, backup, isCCAvailable) ) { walletSnapshot, walletStateInformation, backup, isCCAvailable, shieldFunds ->
}.flatMapLatest { (walletSnapshot, walletStateInformation, backup, isCCAvailable) ->
when { when {
walletSnapshot.synchronizerError != null -> { walletSnapshot.synchronizerError != null -> {
flowOf(HomeMessageData.Error(walletSnapshot.synchronizerError)) HomeMessageData.Error(walletSnapshot.synchronizerError)
} }
walletSnapshot.status == Synchronizer.Status.DISCONNECTED -> { walletSnapshot.status == Synchronizer.Status.DISCONNECTED -> {
flowOf(HomeMessageData.Disconnected) HomeMessageData.Disconnected
} }
walletSnapshot.status in listOf( walletSnapshot.status in listOf(
@ -50,34 +48,35 @@ class GetHomeMessageUseCase(
Synchronizer.Status.SYNCING, Synchronizer.Status.SYNCING,
Synchronizer.Status.STOPPED Synchronizer.Status.STOPPED
) -> { ) -> {
flow { val progress = walletSnapshot.progress.decimal * 100f
val progress = walletSnapshot.progress.decimal * 100f val result = when {
val result = when { walletStateInformation == WalletRestoringState.RESTORING -> {
walletStateInformation == WalletRestoringState.RESTORING -> { HomeMessageData.Restoring(
HomeMessageData.Restoring( progress = progress,
progress = progress, )
) }
}
else -> {
else -> { HomeMessageData.Syncing(progress = progress)
HomeMessageData.Syncing(progress = progress)
}
} }
emit(result)
} }
result
} }
backup is WalletBackupAvailability.Available -> flowOf(HomeMessageData.Backup)
isCCAvailable -> flowOf(HomeMessageData.EnableCurrencyConversion) shieldFunds is ShieldFundsData.Available -> HomeMessageData.ShieldFunds(shieldFunds.amount)
else -> flowOf(null) backup is WalletBackupAvailability.Available -> HomeMessageData.Backup
isCCAvailable -> HomeMessageData.EnableCurrencyConversion
else -> null
} }
}.debounce(.5.seconds) }.debounce(.5.seconds)
} }
sealed interface HomeMessageData { sealed interface HomeMessageData {
data object EnableCurrencyConversion : HomeMessageData data object EnableCurrencyConversion : HomeMessageData
data class TransparentBalance(val zatoshi: Zatoshi) : HomeMessageData data class ShieldFunds(val zatoshi: Zatoshi) : HomeMessageData
data object Backup : HomeMessageData data object Backup : HomeMessageData
data object Disconnected : HomeMessageData data object Disconnected : HomeMessageData
data class Error(val synchronizerError: SynchronizerError) : HomeMessageData data class Error(val synchronizerError: SynchronizerError) : HomeMessageData

View File

@ -1,9 +0,0 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.datasource.ZashiSpendingKeyDataSource
class GetZashiSpendingKeyUseCase(
private val spendingKeyDataSource: ZashiSpendingKeyDataSource,
) {
suspend operator fun invoke() = spendingKeyDataSource.getZashiSpendingKey()
}

View File

@ -1,9 +0,0 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
class ObserveOnAccountChangedUseCase(
private val accountDataSource: AccountDataSource
) {
operator fun invoke() = accountDataSource.onAccountChanged
}

View File

@ -8,7 +8,7 @@ class OnUserSavedWalletBackupUseCase(
private val walletBackupDataSource: WalletBackupDataSource private val walletBackupDataSource: WalletBackupDataSource
) { ) {
suspend operator fun invoke() { suspend operator fun invoke() {
walletBackupDataSource.remindMeLater() walletBackupDataSource.onUserSavedWalletBackup()
navigationRouter.backToRoot() navigationRouter.backToRoot()
} }
} }

View File

@ -0,0 +1,15 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
class RemindShieldFundsLaterUseCase(
private val navigationRouter: NavigationRouter,
private val shieldFundsRepository: ShieldFundsRepository
) {
suspend operator fun invoke() {
shieldFundsRepository.remindMeLater()
navigationRouter.backToRoot()
}
}

View File

@ -2,12 +2,17 @@ package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.NavigationRouter import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.provider.WalletBackupConsentStorageProvider
class RemindWalletBackupLaterUseCase( class RemindWalletBackupLaterUseCase(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val walletBackupDataSource: WalletBackupDataSource private val walletBackupDataSource: WalletBackupDataSource,
private val walletBackupConsentStorageProvider: WalletBackupConsentStorageProvider
) { ) {
suspend operator fun invoke() { suspend operator fun invoke(persistConsent: Boolean) {
if (persistConsent) {
walletBackupConsentStorageProvider.store(true)
}
walletBackupDataSource.remindMeLater() walletBackupDataSource.remindMeLater()
navigationRouter.backToRoot() navigationRouter.backToRoot()
} }

View File

@ -0,0 +1,101 @@
package co.electriccoin.zcash.ui.common.usecase
import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.model.KeystoneAccount
import co.electriccoin.zcash.ui.common.model.SubmitResult
import co.electriccoin.zcash.ui.common.model.ZashiAccount
import co.electriccoin.zcash.ui.common.repository.KeystoneProposalRepository
import co.electriccoin.zcash.ui.common.repository.SubmitProposalState
import co.electriccoin.zcash.ui.common.repository.ZashiProposalRepository
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.SignKeystoneTransaction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class ShieldFundsUseCase(
private val keystoneProposalRepository: KeystoneProposalRepository,
private val zashiProposalRepository: ZashiProposalRepository,
private val navigationRouter: NavigationRouter,
private val accountDataSource: AccountDataSource
) {
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
operator fun invoke(navigateBackAfterSuccess: Boolean) {
scope.launch {
when (accountDataSource.getSelectedAccount()) {
is KeystoneAccount -> {
createKeystoneShieldProposal()
}
is ZashiAccount -> {
if (navigateBackAfterSuccess) {
navigationRouter.back()
}
shieldZashiFunds()
}
}
}
}
private suspend fun shieldZashiFunds() {
try {
zashiProposalRepository.createShieldProposal()
zashiProposalRepository.submitTransaction()
val result = zashiProposalRepository.submitState
.filterIsInstance<SubmitProposalState.Result>()
.first()
.submitResult
when (result) {
is SubmitResult.Success -> {
// do nothing
// TODO messages
}
is SubmitResult.SimpleTrxFailure.SimpleTrxFailureGrpc -> {
// showShieldingError(ShieldState.FailedGrpc)
// TODO messages
}
is SubmitResult.SimpleTrxFailure -> {
// showShieldingError(
// ShieldState.Failed(
// error = result.toErrorMessage(),
// stackTrace = result.toErrorStacktrace()
// )
// )
// TODO messages
}
is SubmitResult.MultipleTrxFailure -> {
// do nothing
}
}
} finally {
zashiProposalRepository.clear()
}
}
private suspend fun createKeystoneShieldProposal() {
try {
keystoneProposalRepository.createShieldProposal()
keystoneProposalRepository.createPCZTFromProposal()
navigationRouter.forward(SignKeystoneTransaction)
} catch (e: Exception) {
keystoneProposalRepository.clear()
// TODO messages
// showShieldingError(
// ShieldState.Failed(
// error =
// context.getString(
// R.string.balances_shielding_dialog_error_text_below_threshold
// ),
// stackTrace = ""
// )
// )
}
}
}

View File

@ -47,8 +47,8 @@ import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessage
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessage import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessage
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceMessage import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessage
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceMessageState import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessageState
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessage import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessage
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessageState import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessageState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -69,10 +69,11 @@ fun HomeMessage(
) )
val bottomHeight by animateDpAsState( val bottomHeight by animateDpAsState(
targetValue = if (isVisible) BOTTOM_CUTOUT_HEIGHT_DP.dp else TOP_CUTOUT_HEIGHT_DP.dp, targetValue = if (isVisible) BOTTOM_CUTOUT_HEIGHT_DP.dp else TOP_CUTOUT_HEIGHT_DP.dp,
animationSpec = animationSpec = animationSpec(delay = if (isVisible) null else 250.milliseconds)
animationSpec( )
delay = if (isVisible) null else 250.milliseconds val elevation by animateDpAsState(
) targetValue = if (isVisible) 2.dp else 0.dp,
animationSpec = elevationAnimationSpec(delay = if (isVisible) 450.milliseconds else null)
) )
Box( Box(
@ -85,7 +86,7 @@ fun HomeMessage(
.height(TOP_CUTOUT_HEIGHT_DP.dp) .height(TOP_CUTOUT_HEIGHT_DP.dp)
.zIndex(2f) .zIndex(2f)
.bottomOnlyShadow( .bottomOnlyShadow(
elevation = 2.dp, elevation = elevation,
shape = shape =
RoundedCornerShape( RoundedCornerShape(
bottomStart = TOP_CUTOUT_HEIGHT_DP.dp, bottomStart = TOP_CUTOUT_HEIGHT_DP.dp,
@ -128,10 +129,10 @@ fun HomeMessage(
contentPadding = contentPadding contentPadding = contentPadding
) )
is TransparentBalanceMessageState -> is ShieldFundsMessageState ->
TransparentBalanceMessage( ShieldFundsMessage(
innerModifier = innerModifier, innerModifier = innerModifier,
state = normalizedState as TransparentBalanceMessageState, state = normalizedState as ShieldFundsMessageState,
contentPadding = contentPadding contentPadding = contentPadding
) )
@ -184,7 +185,7 @@ fun HomeMessage(
.zIndex(1f) .zIndex(1f)
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.topOnlyShadow( .topOnlyShadow(
elevation = 2.dp, elevation = elevation,
shape = RoundedCornerShape(topStart = bottomCornerSize, topEnd = bottomCornerSize), shape = RoundedCornerShape(topStart = bottomCornerSize, topEnd = bottomCornerSize),
backgroundColor = ZashiColors.Surfaces.bgPrimary backgroundColor = ZashiColors.Surfaces.bgPrimary
), ),
@ -232,7 +233,17 @@ private fun <T> animationSpec(delay: Duration? = null): TweenSpec<T> {
) )
} }
private fun <T> elevationAnimationSpec(delay: Duration? = null): TweenSpec<T> {
val delayMs = delay?.inWholeMilliseconds?.toInt() ?: 0
return tween(
durationMillis = ELEVATION_ANIMATION_DURATION_MS - delayMs,
easing = CubicBezierEasing(0.6f, 0.1f, 0.3f, 0.9f),
delayMillis = delayMs
)
}
private const val ANIMATION_DURATION_MS = 850 private const val ANIMATION_DURATION_MS = 850
private const val ELEVATION_ANIMATION_DURATION_MS = 650
private const val ANIMATION_DURATION_BETWEEN_MESSAGES_MS = 1000 private const val ANIMATION_DURATION_BETWEEN_MESSAGES_MS = 1000
private const val TOP_CUTOUT_HEIGHT_DP = 32 private const val TOP_CUTOUT_HEIGHT_DP = 32
private const val BOTTOM_CUTOUT_HEIGHT_DP = 24 private const val BOTTOM_CUTOUT_HEIGHT_DP = 24

View File

@ -15,29 +15,30 @@ import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.common.usecase.HomeMessageData import co.electriccoin.zcash.ui.common.usecase.HomeMessageData
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToCoinbaseUseCase
import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase
import co.electriccoin.zcash.ui.design.component.BigIconButtonState import co.electriccoin.zcash.ui.design.component.BigIconButtonState
import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptIn import co.electriccoin.zcash.ui.screen.exchangerate.optin.ExchangeRateOptIn
import co.electriccoin.zcash.ui.screen.home.backup.SeedBackupInfo import co.electriccoin.zcash.ui.screen.home.backup.SeedBackupInfo
import co.electriccoin.zcash.ui.screen.home.currency.EnableCurrencyConversionMessageState import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupDetail
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceMessageState
import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupMessageState import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupMessageState
import co.electriccoin.zcash.ui.screen.home.currency.EnableCurrencyConversionMessageState
import co.electriccoin.zcash.ui.screen.home.disconnected.WalletDisconnectedInfo import co.electriccoin.zcash.ui.screen.home.disconnected.WalletDisconnectedInfo
import co.electriccoin.zcash.ui.screen.home.disconnected.WalletDisconnectedMessageState import co.electriccoin.zcash.ui.screen.home.disconnected.WalletDisconnectedMessageState
import co.electriccoin.zcash.ui.screen.home.error.WalletErrorMessageState import co.electriccoin.zcash.ui.screen.home.error.WalletErrorMessageState
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessageState
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfo import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfo
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessageState
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingInfo import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingInfo
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceInfo import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingInfo import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingInfo
import co.electriccoin.zcash.ui.screen.home.updating.WalletUpdatingMessageState
import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations
import co.electriccoin.zcash.ui.screen.receive.Receive import co.electriccoin.zcash.ui.screen.receive.Receive
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
import co.electriccoin.zcash.ui.screen.scan.Scan import co.electriccoin.zcash.ui.screen.scan.Scan
import co.electriccoin.zcash.ui.screen.scan.ScanFlow import co.electriccoin.zcash.ui.screen.scan.ScanFlow
import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupDetail
import co.electriccoin.zcash.ui.screen.send.Send import co.electriccoin.zcash.ui.screen.send.Send
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@ -55,6 +56,7 @@ class HomeViewModel(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase, private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase,
private val navigateToCoinbase: NavigateToCoinbaseUseCase, private val navigateToCoinbase: NavigateToCoinbaseUseCase,
private val shieldFunds: ShieldFundsUseCase
) : ViewModel() { ) : ViewModel() {
private val messageState = getHomeMessage private val messageState = getHomeMessage
@ -177,10 +179,13 @@ class HomeViewModel(
onClick = ::onWalletSyncingMessageClick onClick = ::onWalletSyncingMessageClick
) )
is HomeMessageData.TransparentBalance -> TransparentBalanceMessageState( is HomeMessageData.ShieldFunds -> ShieldFundsMessageState(
subtitle = stringRes(zatoshi = it.zatoshi), subtitle = stringRes(
onClick = ::onTransparentBalanceMessageClick, R.string.home_message_transparent_balance_subtitle,
onButtonClick = ::onTransparentBalanceMessageButtonClick, stringRes(it.zatoshi)
),
onClick = ::onShieldFundsMessageClick,
onButtonClick = ::onShieldFundsMessageButtonClick,
) )
HomeMessageData.Updating -> WalletUpdatingMessageState( HomeMessageData.Updating -> WalletUpdatingMessageState(
@ -190,73 +195,40 @@ class HomeViewModel(
null -> null null -> null
} }
private fun onRestoreDialogSeenClick() = private fun onRestoreDialogSeenClick() = viewModelScope.launch { isRestoreSuccessDialogVisible.setSeen() }
viewModelScope.launch {
isRestoreSuccessDialogVisible.setSeen()
}
private fun onMoreButtonClick() { private fun onMoreButtonClick() = navigationRouter.forward(DialogIntegrations)
navigationRouter.forward(DialogIntegrations)
}
private fun onSendButtonClick() { private fun onSendButtonClick() = navigationRouter.forward(Send())
navigationRouter.forward(Send())
}
private fun onReceiveButtonClick() { private fun onReceiveButtonClick() = navigationRouter.forward(Receive)
navigationRouter.forward(Receive)
}
private fun onScanButtonClick() { private fun onScanButtonClick() = navigationRouter.forward(Scan(ScanFlow.HOMEPAGE))
navigationRouter.forward(Scan(ScanFlow.HOMEPAGE))
}
private fun onBuyClick() = private fun onBuyClick() = viewModelScope.launch { navigateToCoinbase(replaceCurrentScreen = false) }
viewModelScope.launch {
navigateToCoinbase(replaceCurrentScreen = false)
}
private fun onRequestClick() { private fun onRequestClick() =
navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}") navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}")
}
private fun onWalletUpdatingMessageClick() { private fun onWalletUpdatingMessageClick() = navigationRouter.forward(WalletUpdatingInfo)
navigationRouter.forward(WalletUpdatingInfo)
}
private fun onWalletSyncingMessageClick() { private fun onWalletSyncingMessageClick() = navigationRouter.forward(WalletSyncingInfo)
navigationRouter.forward(WalletSyncingInfo)
}
private fun onWalletRestoringMessageClick() { private fun onWalletRestoringMessageClick() = navigationRouter.forward(WalletRestoringInfo)
navigationRouter.forward(WalletRestoringInfo)
}
private fun onEnableCurrencyConversionClick() { private fun onEnableCurrencyConversionClick() = navigationRouter.forward(ExchangeRateOptIn)
navigationRouter.forward(ExchangeRateOptIn)
}
private fun onWalletDisconnectedMessageClick() { private fun onWalletDisconnectedMessageClick() = navigationRouter.forward(WalletDisconnectedInfo)
navigationRouter.forward(WalletDisconnectedInfo)
}
private fun onWalletBackupMessageClick() { private fun onWalletBackupMessageClick() = navigationRouter.forward(SeedBackupInfo)
navigationRouter.forward(SeedBackupInfo)
}
private fun onWalletBackupMessageButtonClick() { private fun onWalletBackupMessageButtonClick() = navigationRouter.forward(WalletBackupDetail(false))
navigationRouter.forward(WalletBackupDetail(false))
}
private fun onTransparentBalanceMessageClick() { private fun onShieldFundsMessageClick() = navigationRouter.forward(ShieldFundsInfo)
navigationRouter.forward(TransparentBalanceInfo)
}
private fun onTransparentBalanceMessageButtonClick(): Nothing { private fun onShieldFundsMessageButtonClick() = shieldFunds(navigateBackAfterSuccess = false)
TODO()
}
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error): Nothing { private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) {
// statusText = // statusText =
// context.getString( // context.getString(
// R.string.balances_status_error_simple, // R.string.balances_status_error_simple,

View File

@ -1,10 +1,12 @@
package co.electriccoin.zcash.ui.screen.home.backup package co.electriccoin.zcash.ui.screen.home.backup
import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.CheckboxState
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
data class WalletBackupInfoState( data class WalletBackupInfoState(
override val onBack: () -> Unit, override val onBack: () -> Unit,
val checkboxState: CheckboxState?,
val primaryButton: ButtonState, val primaryButton: ButtonState,
val secondaryButton: ButtonState val secondaryButton: ButtonState
) : ModalBottomSheetState ) : ModalBottomSheetState

View File

@ -4,6 +4,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState import androidx.compose.material3.SheetState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -15,10 +17,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.CheckboxState
import co.electriccoin.zcash.ui.design.component.Spacer import co.electriccoin.zcash.ui.design.component.Spacer
import co.electriccoin.zcash.ui.design.component.ZashiBulletText import co.electriccoin.zcash.ui.design.component.ZashiBulletText
import co.electriccoin.zcash.ui.design.component.ZashiButton import co.electriccoin.zcash.ui.design.component.ZashiButton
import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults
import co.electriccoin.zcash.ui.design.component.ZashiCheckbox
import co.electriccoin.zcash.ui.design.component.ZashiScreenModalBottomSheet import co.electriccoin.zcash.ui.design.component.ZashiScreenModalBottomSheet
import co.electriccoin.zcash.ui.design.component.rememberScreenModalBottomSheetState import co.electriccoin.zcash.ui.design.component.rememberScreenModalBottomSheetState
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
@ -38,9 +42,11 @@ fun WalletBackupInfoView(
state = state state = state
) { ) {
Column( Column(
modifier = modifier = Modifier
Modifier .weight(1f, false)
.padding(horizontal = 24.dp) .verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp)
) { ) {
Image( Image(
painter = painterResource(R.drawable.ic_info_backup), painter = painterResource(R.drawable.ic_info_backup),
@ -84,6 +90,12 @@ fun WalletBackupInfoView(
style = ZashiTypography.textMd style = ZashiTypography.textMd
) )
Spacer(32.dp) Spacer(32.dp)
it.checkboxState?.let { checkbox ->
ZashiCheckbox(
state = checkbox
)
Spacer(12.dp)
}
ZashiButton( ZashiButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = it.secondaryButton, state = it.secondaryButton,
@ -108,11 +120,17 @@ private fun Preview() =
onBack = {}, onBack = {},
secondaryButton = ButtonState( secondaryButton = ButtonState(
text = stringRes(R.string.general_remind_me_later), text = stringRes(R.string.general_remind_me_later),
onClick = {} onClick = {},
isEnabled = false
), ),
primaryButton = ButtonState( primaryButton = ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = {} onClick = {}
),
checkboxState = CheckboxState(
isChecked = false,
onClick = {},
text = stringRes(R.string.home_info_backup_checkbox)
) )
) )
) )

View File

@ -2,38 +2,83 @@ package co.electriccoin.zcash.ui.screen.home.backup
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.NavigationRouter import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.WalletBackupAvailability
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.provider.WalletBackupConsentStorageProvider
import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase
import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.CheckboxState
import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class WalletBackupInfoViewModel( class WalletBackupInfoViewModel(
walletBackupDataSource: WalletBackupDataSource,
walletBackupConsentStorageProvider: WalletBackupConsentStorageProvider,
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : ViewModel() { ) : ViewModel() {
val state: StateFlow<WalletBackupInfoState?> = MutableStateFlow(
WalletBackupInfoState( private val isConsentChecked = MutableStateFlow(false)
onBack = ::onBack,
secondaryButton = ButtonState( private val lockoutDuration = walletBackupDataSource
text = stringRes(R.string.general_remind_me_later), .observe()
onClick = ::onRemindMeLaterClick .filterIsInstance<WalletBackupAvailability.Available>()
), .take(1)
primaryButton = ButtonState( .map { it.lockoutDuration }
text = stringRes(R.string.general_ok), .stateIn(
onClick = ::onPrimaryClick scope = viewModelScope,
) started = SharingStarted.WhileSubscribed(),
initialValue = null
) )
)
private fun onPrimaryClick() { val state: StateFlow<WalletBackupInfoState?> = combine(
navigationRouter.replace(WalletBackupDetail(true)) lockoutDuration.filterNotNull(),
} isConsentChecked,
walletBackupConsentStorageProvider.observe().take(1)
) { lockout, isConsentChecked, isConsentSaved ->
WalletBackupInfoState(
onBack = ::onBack,
secondaryButton = ButtonState(
text = stringRes(R.string.general_remind_me_in, stringRes(lockout.res)),
onClick = ::onRemindMeLaterClick,
isEnabled = isConsentChecked || isConsentSaved
),
checkboxState = CheckboxState(
isChecked = isConsentChecked,
onClick = ::onConsentClick,
text = stringRes(R.string.home_info_backup_checkbox)
).takeIf { !isConsentSaved },
primaryButton = ButtonState(
text = stringRes(R.string.general_ok),
onClick = ::onPrimaryClick
)
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null
)
private fun onRemindMeLaterClick() = viewModelScope.launch { remindWalletBackupLater() } private fun onConsentClick() = isConsentChecked.update { !it }
private fun onPrimaryClick() = navigationRouter.replace(WalletBackupDetail(true))
private fun onRemindMeLaterClick() = viewModelScope.launch { remindWalletBackupLater(persistConsent = true) }
private fun onBack() = navigationRouter.back() private fun onBack() = navigationRouter.back()
} }

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.screen.home.transparentbalance package co.electriccoin.zcash.ui.screen.home.shieldfunds
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -9,11 +9,11 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AndroidTransparentBalanceInfo() { fun AndroidShieldFundsInfo() {
val vm = koinViewModel<TransparentBalanceInfoViewModel>() val vm = koinViewModel<ShieldFundsInfoViewModel>()
val state by vm.state.collectAsStateWithLifecycle() val state by vm.state.collectAsStateWithLifecycle()
state?.let { TransparentBalanceInfoView(it) } ShieldFundsInfoView(state)
} }
@Serializable @Serializable
object TransparentBalanceInfo object ShieldFundsInfo

View File

@ -1,10 +1,10 @@
package co.electriccoin.zcash.ui.screen.home.transparentbalance package co.electriccoin.zcash.ui.screen.home.shieldfunds
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
data class TransparentBalanceInfoState( data class ShieldFundsInfoState(
val transparentAmount: Zatoshi, val transparentAmount: Zatoshi,
override val onBack: () -> Unit, override val onBack: () -> Unit,
val primaryButton: ButtonState, val primaryButton: ButtonState,

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.screen.home.transparentbalance package co.electriccoin.zcash.ui.screen.home.shieldfunds
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -34,8 +34,8 @@ import co.electriccoin.zcash.ui.design.util.stringRes
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun TransparentBalanceInfoView( fun ShieldFundsInfoView(
state: TransparentBalanceInfoState?, state: ShieldFundsInfoState?,
sheetState: SheetState = rememberScreenModalBottomSheetState(), sheetState: SheetState = rememberScreenModalBottomSheetState(),
) { ) {
ZashiScreenModalBottomSheet( ZashiScreenModalBottomSheet(
@ -47,7 +47,7 @@ fun TransparentBalanceInfoView(
} }
@Composable @Composable
private fun Content(state: TransparentBalanceInfoState) { private fun Content(state: ShieldFundsInfoState) {
Column( Column(
modifier = modifier =
Modifier Modifier
@ -132,9 +132,9 @@ private fun Content(state: TransparentBalanceInfoState) {
@Composable @Composable
private fun Preview() = private fun Preview() =
ZcashTheme { ZcashTheme {
TransparentBalanceInfoView( ShieldFundsInfoView(
state = state =
TransparentBalanceInfoState( ShieldFundsInfoState(
onBack = {}, onBack = {},
primaryButton = primaryButton =
ButtonState( ButtonState(

View File

@ -0,0 +1,79 @@
package co.electriccoin.zcash.ui.screen.home.shieldfunds
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 co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.WalletBackupAvailability
import co.electriccoin.zcash.ui.common.repository.ShieldFundsData
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.common.usecase.RemindShieldFundsLaterUseCase
import co.electriccoin.zcash.ui.common.usecase.ShieldFundsUseCase
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
class ShieldFundsInfoViewModel(
getSelectedWalletAccount: GetSelectedWalletAccountUseCase,
shieldFundsRepository: ShieldFundsRepository,
private val navigationRouter: NavigationRouter,
private val remindShieldFundsLater: RemindShieldFundsLaterUseCase,
private val shieldFunds: ShieldFundsUseCase,
) : ViewModel() {
private val lockoutDuration = shieldFundsRepository
.availability
.filterIsInstance<ShieldFundsData.Available>()
.take(1)
.map { it.lockoutDuration }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null
)
val state: StateFlow<ShieldFundsInfoState?> =
combine(
getSelectedWalletAccount.observe(),
lockoutDuration.filterNotNull(),
) { account, lockoutDuration ->
ShieldFundsInfoState(
onBack = ::onBack,
primaryButton =
ButtonState(
onClick = ::onShieldClick,
text = stringRes(R.string.home_info_transparent_balance_shield)
),
secondaryButton =
ButtonState(
onClick = ::onRemindMeClick,
text = stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
),
transparentAmount = account?.transparent?.balance ?: Zatoshi(0)
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null
)
private fun onRemindMeClick() = viewModelScope.launch { remindShieldFundsLater() }
private fun onBack() = navigationRouter.back()
private fun onShieldClick() = shieldFunds(navigateBackAfterSuccess = true)
}

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.screen.home.transparentbalance package co.electriccoin.zcash.ui.screen.home.shieldfunds
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -26,9 +26,9 @@ import co.electriccoin.zcash.ui.screen.home.HomeMessageWrapper
@Suppress("ModifierNaming") @Suppress("ModifierNaming")
@Composable @Composable
fun TransparentBalanceMessage( fun ShieldFundsMessage(
contentPadding: PaddingValues, contentPadding: PaddingValues,
state: TransparentBalanceMessageState, state: ShieldFundsMessageState,
innerModifier: Modifier = Modifier, innerModifier: Modifier = Modifier,
) { ) {
HomeMessageWrapper( HomeMessageWrapper(
@ -65,7 +65,7 @@ fun TransparentBalanceMessage(
) )
} }
class TransparentBalanceMessageState( class ShieldFundsMessageState(
val subtitle: StringResource, val subtitle: StringResource,
val onClick: () -> Unit, val onClick: () -> Unit,
val onButtonClick: () -> Unit, val onButtonClick: () -> Unit,
@ -76,9 +76,9 @@ class TransparentBalanceMessageState(
private fun Preview() = private fun Preview() =
ZcashTheme { ZcashTheme {
BlankSurface { BlankSurface {
TransparentBalanceMessage( ShieldFundsMessage(
state = state =
TransparentBalanceMessageState( ShieldFundsMessageState(
subtitle = subtitle =
stringRes( stringRes(
R.string.home_message_transparent_balance_subtitle, R.string.home_message_transparent_balance_subtitle,

View File

@ -1,45 +0,0 @@
package co.electriccoin.zcash.ui.screen.home.transparentbalance
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 co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class TransparentBalanceInfoViewModel(
getSelectedWalletAccount: GetSelectedWalletAccountUseCase,
private val navigationRouter: NavigationRouter
) : ViewModel() {
val state: StateFlow<TransparentBalanceInfoState?> =
getSelectedWalletAccount
.observe()
.map {
TransparentBalanceInfoState(
onBack = { navigationRouter.back() },
primaryButton =
ButtonState(
onClick = { navigationRouter.back() },
text = stringRes(R.string.home_info_transparent_balance_shield)
),
secondaryButton =
ButtonState(
onClick = { navigationRouter.back() },
text = stringRes(R.string.general_remind_me_later)
),
transparentAmount = it?.transparent?.balance ?: Zatoshi(0)
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null
)
}

View File

@ -11,7 +11,6 @@ import co.electriccoin.zcash.ui.common.model.KeystoneAccount
import co.electriccoin.zcash.ui.common.model.WalletAccount import co.electriccoin.zcash.ui.common.model.WalletAccount
import co.electriccoin.zcash.ui.common.model.ZashiAccount import co.electriccoin.zcash.ui.common.model.ZashiAccount
import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveOnAccountChangedUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedWalletAccountUseCase import co.electriccoin.zcash.ui.common.usecase.ObserveSelectedWalletAccountUseCase
import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH import co.electriccoin.zcash.ui.screen.addressbook.viewmodel.ADDRESS_MAX_LENGTH
@ -24,11 +23,9 @@ import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class ReceiveViewModel( class ReceiveViewModel(
observeSelectedWalletAccount: ObserveSelectedWalletAccountUseCase, observeSelectedWalletAccount: ObserveSelectedWalletAccountUseCase,
observeOnAccountChanged: ObserveOnAccountChangedUseCase,
private val application: Application, private val application: Application,
private val copyToClipboard: CopyToClipboardUseCase, private val copyToClipboard: CopyToClipboardUseCase,
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
@ -69,14 +66,6 @@ class ReceiveViewModel(
) )
) )
init {
viewModelScope.launch {
observeOnAccountChanged().collect {
expandedIndex.update { 0 }
}
}
}
private fun onBack() = navigationRouter.back() private fun onBack() = navigationRouter.back()
private fun createAddressState( private fun createAddressState(

View File

@ -1,65 +0,0 @@
package co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.model.SubmitResult
import kotlinx.coroutines.flow.MutableStateFlow
class CreateTransactionsViewModel : ViewModel() {
// Technically this value will not survive process dead, but will survive all possible configuration changes
// Possible solution would be storing the value within [SavedStateHandle]
val submissions: MutableStateFlow<List<TransactionSubmitResult>> = MutableStateFlow(emptyList())
suspend fun runCreateTransactions(
synchronizer: Synchronizer,
spendingKey: UnifiedSpendingKey,
proposal: Proposal
): SubmitResult {
val submitResults = mutableListOf<TransactionSubmitResult>()
return runCatching {
synchronizer
.createProposedTransactions(
proposal = proposal,
usk = spendingKey
).collect { submitResult ->
Twig.info { "Transaction submit result: $submitResult" }
submitResults.add(submitResult)
}
if (submitResults.find { it is TransactionSubmitResult.Failure } != null) {
if (submitResults.size == 1) {
// The first transaction submission failed - user might just be able to re-submit the transaction
// proposal. Simple error pop up is fine then
val result = (submitResults[0] as TransactionSubmitResult.Failure)
if (result.grpcError) {
SubmitResult.SimpleTrxFailure.SimpleTrxFailureGrpc(result)
} else {
SubmitResult.SimpleTrxFailure.SimpleTrxFailureSubmit(result)
}
} else {
// Any subsequent transaction submission failed - user needs to resolve this manually. Multiple
// transaction failure screen presented
SubmitResult.MultipleTrxFailure(submitResults)
}
} else {
// All transaction submissions were successful
SubmitResult.Success(emptyList())
}
}.onSuccess {
Twig.debug { "Transactions submitted successfully" }
}.onFailure {
Twig.error(it) { "Transactions submission failed" }
}.getOrElse {
SubmitResult.SimpleTrxFailure.SimpleTrxFailureOther(it)
}.also {
// Save the submission results for the later MultipleSubmissionError screen
if (it is SubmitResult.MultipleTrxFailure) {
submissions.value = submitResults
}
}
}
}

View File

@ -13,7 +13,5 @@ import org.koin.androidx.compose.koinViewModel
fun AndroidTransactionFiltersList() { fun AndroidTransactionFiltersList() {
val viewModel = koinViewModel<TransactionFiltersViewModel>() val viewModel = koinViewModel<TransactionFiltersViewModel>()
val state by viewModel.state.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
TransactionFiltersView( TransactionFiltersView(state = state)
state = state,
)
} }

View File

@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.NavigationRouter import co.electriccoin.zcash.ui.NavigationRouter
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.datasource.WalletBackupAvailability
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.OnUserSavedWalletBackupUseCase import co.electriccoin.zcash.ui.common.usecase.OnUserSavedWalletBackupUseCase
import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase import co.electriccoin.zcash.ui.common.usecase.RemindWalletBackupLaterUseCase
@ -17,18 +19,33 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class WalletBackupViewModel( class WalletBackupViewModel(
private val args: WalletBackup, walletBackupDataSource: WalletBackupDataSource,
observePersistableWallet: ObservePersistableWalletUseCase, observePersistableWallet: ObservePersistableWalletUseCase,
private val args: WalletBackup,
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase, private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : ViewModel() { ) : ViewModel() {
private val lockoutDuration = walletBackupDataSource
.observe()
.filterIsInstance<WalletBackupAvailability.Available>()
.take(1)
.map { it.lockoutDuration }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null
)
private val isRevealed = MutableStateFlow(false) private val isRevealed = MutableStateFlow(false)
private val isRemindMeLaterButtonVisible = isRevealed private val isRemindMeLaterButtonVisible = isRevealed
@ -42,11 +59,16 @@ class WalletBackupViewModel(
combine( combine(
isRevealed, isRevealed,
isRemindMeLaterButtonVisible, isRemindMeLaterButtonVisible,
observableWallet observableWallet,
) { isRevealed, isRemindMeLaterButtonVisible, wallet -> lockoutDuration
) { isRevealed, isRemindMeLaterButtonVisible, wallet, lockoutDuration ->
WalletBackupState( WalletBackupState(
secondaryButton = ButtonState( secondaryButton = ButtonState(
text = stringRes(R.string.general_remind_me_later), text = if (lockoutDuration != null) {
stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
} else {
stringRes(R.string.general_remind_me_later)
},
onClick = ::onRemindMeLaterClick onClick = ::onRemindMeLaterClick
).takeIf { isRemindMeLaterButtonVisible }, ).takeIf { isRemindMeLaterButtonVisible },
primaryButton = primaryButton =
@ -105,10 +127,7 @@ class WalletBackupViewModel(
) )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
private fun onRemindMeLaterClick() = private fun onRemindMeLaterClick() = viewModelScope.launch { remindWalletBackupLater(persistConsent = false) }
viewModelScope.launch {
remindWalletBackupLater()
}
private fun onWalletBackupSavedClick() = private fun onWalletBackupSavedClick() =
viewModelScope.launch { viewModelScope.launch {

View File

@ -30,4 +30,8 @@
<string name="general_ok">OK</string> <string name="general_ok">OK</string>
<string name="general_remind_me_later">Remind me later</string> <string name="general_remind_me_later">Remind me later</string>
<string name="general_remind_me_in">Remind me in %s</string>
<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>
</resources> </resources>

View File

@ -36,6 +36,7 @@
Recovery Phrase is the only way to recover your funds. We cannot see it and cannot help you recover it.</string> Recovery Phrase is the only way to recover your funds. We cannot see it and cannot help you recover it.</string>
<string name="home_info_backup_message_2">Anyone with access to your Secret Recovery Phrase will have full control <string name="home_info_backup_message_2">Anyone with access to your Secret Recovery Phrase will have full control
of your wallet, so keep it secure and never show it to anyone.</string> of your wallet, so keep it secure and never show it to anyone.</string>
<string name="home_info_backup_checkbox">I read and understand the risks of not backing up my wallet.</string>
<string name="home_info_transparent_balance_shield">Shield</string> <string name="home_info_transparent_balance_shield">Shield</string>
<string name="home_info_transparent_title">Always Shield Transparent Funds</string> <string name="home_info_transparent_title">Always Shield Transparent Funds</string>
<string name="home_info_transparent_subtitle">To protect user privacy, Zashi doesn\'t support spending transparent <string name="home_info_transparent_subtitle">To protect user privacy, Zashi doesn\'t support spending transparent

View File

@ -36,6 +36,7 @@
Recovery Phrase is the only way to recover your funds. We cannot see it and cannot help you recover it.</string> Recovery Phrase is the only way to recover your funds. We cannot see it and cannot help you recover it.</string>
<string name="home_info_backup_message_2">Anyone with access to your Secret Recovery Phrase will have full control <string name="home_info_backup_message_2">Anyone with access to your Secret Recovery Phrase will have full control
of your wallet, so keep it secure and never show it to anyone.</string> of your wallet, so keep it secure and never show it to anyone.</string>
<string name="home_info_backup_checkbox">I read and understand the risks of not backing up my wallet.</string>
<string name="home_info_transparent_balance_shield">Shield</string> <string name="home_info_transparent_balance_shield">Shield</string>
<string name="home_info_transparent_title">Always Shield Transparent Funds</string> <string name="home_info_transparent_title">Always Shield Transparent Funds</string>
<string name="home_info_transparent_subtitle">To protect user privacy, Zashi doesn\'t support spending transparent <string name="home_info_transparent_subtitle">To protect user privacy, Zashi doesn\'t support spending transparent