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,
borderColor: Color = Color.Unspecified,
disabledContainerColor: Color = source.Btns.Secondary.btnSecondaryBgDisabled,
disabledContentColor: Color = source.Btns.Secondary.btnSecondaryFg,
disabledContentColor: Color = source.Btns.Secondary.btnSecondaryFgDisabled,
) = ZashiButtonColors(
containerColor = containerColor,
contentColor = contentColor,

View File

@ -2,6 +2,7 @@ package co.electriccoin.zcash.ui.design.component
import android.view.WindowManager
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
@ -27,7 +28,7 @@ import co.electriccoin.zcash.ui.design.LocalSheetStateManager
fun <T : ModalBottomSheetState> ZashiScreenModalBottomSheet(
state: T?,
sheetState: SheetState = rememberScreenModalBottomSheetState(),
content: @Composable (state: T) -> Unit = {},
content: @Composable ColumnScope.(state: T) -> Unit = {},
) {
val parent = LocalView.current.parent
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.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.ProposalDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
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.WalletBackupDataSourceImpl
import co.electriccoin.zcash.ui.common.datasource.ZashiSpendingKeyDataSource
@ -21,4 +25,6 @@ val dataSourceModule =
singleOf(::ProposalDataSourceImpl) bind ProposalDataSource::class
singleOf(::RestoreTimestampDataSourceImpl) bind RestoreTimestampDataSource::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.SynchronizerProvider
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.WalletBackupFlagStorageProviderImpl
import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeCountStorageProvider
@ -49,4 +51,5 @@ val providerModule =
factoryOf(::WalletBackupRemindMeTimestampStorageProviderImpl) bind
WalletBackupRemindMeTimestampStorageProvider::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.KeystoneProposalRepository
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.TransactionFilterRepositoryImpl
import co.electriccoin.zcash.ui.common.repository.TransactionRepository
@ -33,4 +35,5 @@ val repositoryModule =
singleOf(::TransactionRepositoryImpl) bind TransactionRepository::class
singleOf(::TransactionFilterRepositoryImpl) bind TransactionFilterRepository::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.CreateKeystoneAccountUseCase
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.CreateProposalUseCase
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.GetWalletRestoringStateUseCase
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.IsFlexaAvailableUseCase
import co.electriccoin.zcash.ui.common.usecase.IsRestoreSuccessDialogVisibleUseCase
import co.electriccoin.zcash.ui.common.usecase.MarkTxMemoAsReadUseCase
import co.electriccoin.zcash.ui.common.usecase.NavigateToAddressBookUseCase
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.NavigateToWalletBackupUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveAddressBookContactsUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveClearSendUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveContactByAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.ObserveContactPickedUseCase
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.ObserveProposalUseCase
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.PrefillSendUseCase
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.RescanBlockchainUseCase
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.ShareImageUseCase
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.ValidateContactAddressUseCase
import co.electriccoin.zcash.ui.common.usecase.ValidateContactNameUseCase
@ -125,7 +124,6 @@ val useCaseModule =
factoryOf(::Zip321BuildUriUseCase)
factoryOf(::Zip321ParseUriValidationUseCase)
factoryOf(::IsCoinbaseAvailableUseCase)
factoryOf(::GetZashiSpendingKeyUseCase)
factoryOf(::ObservePersistableWalletUseCase)
factoryOf(::GetSupportUseCase)
factoryOf(::SendEmailUseCase)
@ -148,14 +146,12 @@ val useCaseModule =
factoryOf(::CreateProposalUseCase)
factoryOf(::OnZip321ScannedUseCase)
factoryOf(::OnAddressScannedUseCase)
factoryOf(::CreateKeystoneShieldProposalUseCase)
factoryOf(::ParseKeystonePCZTUseCase)
factoryOf(::ParseKeystoneSignInRequestUseCase)
factoryOf(::CancelProposalFlowUseCase)
factoryOf(::ObserveProposalUseCase)
factoryOf(::SharePCZTUseCase)
factoryOf(::CreateKeystoneProposalPCZTEncoderUseCase)
factoryOf(::ObserveOnAccountChangedUseCase)
factoryOf(::ViewTransactionsAfterSuccessfulProposalUseCase)
factoryOf(::ViewTransactionDetailAfterSuccessfulProposalUseCase)
factoryOf(::NavigateToCoinbaseUseCase)
@ -192,4 +188,6 @@ val useCaseModule =
factoryOf(::GetHomeMessageUseCase)
factoryOf(::OnUserSavedWalletBackupUseCase)
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.backup.WalletBackupDetailViewModel
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.qrcode.viewmodel.QrCodeViewModel
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.viewmodel.SelectKeystoneAccountViewModel
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.SettingsViewModel
import co.electriccoin.zcash.ui.screen.signkeystonetransaction.viewmodel.SignKeystoneTransactionViewModel
@ -69,7 +68,6 @@ val viewModelModule =
viewModelOf(::SettingsViewModel)
viewModelOf(::AdvancedSettingsViewModel)
viewModelOf(::SupportViewModel)
viewModelOf(::CreateTransactionsViewModel)
viewModelOf(::RestoreSuccessViewModel)
viewModelOf(::WhatsNewViewModel)
viewModelOf(::ChooseServerViewModel)
@ -147,7 +145,7 @@ val viewModelModule =
viewModelOf(::RestoreBDHeightViewModel)
viewModelOf(::RestoreBDDateViewModel)
viewModelOf(::RestoreBDEstimationViewModel)
viewModelOf(::TransparentBalanceInfoViewModel)
viewModelOf(::ShieldFundsInfoViewModel)
viewModelOf(::WalletBackupInfoViewModel)
viewModelOf(::ExchangeRateOptInViewModel)
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.syncing.WalletSyncingInfo
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.transparentbalance.TransparentBalanceInfo
import co.electriccoin.zcash.ui.screen.home.shieldfunds.AndroidShieldFundsInfo
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo
import co.electriccoin.zcash.ui.screen.integrations.AndroidDialogIntegrations
import co.electriccoin.zcash.ui.screen.integrations.AndroidIntegrations
import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations
@ -422,14 +422,14 @@ internal fun MainActivity.Navigation() {
) {
AndroidWalletBackupInfo()
}
dialog<TransparentBalanceInfo>(
dialog<ShieldFundsInfo>(
dialogProperties =
DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
AndroidTransparentBalanceInfo()
AndroidShieldFundsInfo()
}
dialog<WalletDisconnectedInfo>(
dialogProperties =

View File

@ -24,7 +24,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@ -37,13 +36,10 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.seconds
interface AccountDataSource {
val onAccountChanged: Flow<Unit>
val allAccounts: StateFlow<List<WalletAccount>?>
val selectedAccount: Flow<WalletAccount?>
@ -74,8 +70,6 @@ class AccountDataSourceImpl(
) : AccountDataSource {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override val onAccountChanged = MutableSharedFlow<Unit>()
@OptIn(ExperimentalCoroutinesApi::class)
private val internalAccounts: Flow<List<InternalAccountWithBalances>?> =
synchronizerProvider
@ -224,15 +218,7 @@ class AccountDataSourceImpl(
override suspend fun selectAccount(account: Account) {
withContext(Dispatchers.IO) {
val current = selectedAccountUUIDProvider.getUUID()
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
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.WalletBackupRemindMeCountStorageProvider
import co.electriccoin.zcash.ui.common.provider.WalletBackupRemindMeTimestampStorageProvider
@ -38,21 +40,21 @@ class WalletBackupDataSourceImpl(
}.flatMapLatest { (isBackedUp, count, timestamp) ->
when {
isBackedUp -> flowOf(WalletBackupAvailability.Unavailable)
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.ONE_DAY))
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS))
count == 1 -> calculateNext(
lastTimestamp = timestamp,
lastLockoutDuration = WalletBackupLockoutDuration.ONE_DAY,
nextLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS
lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS,
nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS
)
else -> calculateNext(
lastTimestamp = timestamp,
lastLockoutDuration = if (count == 2) {
WalletBackupLockoutDuration.TWO_DAYS
WalletBackupLockoutDuration.TWO_WEEKS
} 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
}
enum class WalletBackupLockoutDuration(val duration: Duration) {
ONE_DAY(10.seconds),
TWO_DAYS(20.seconds),
THREE_DAYS(30.seconds)
enum class WalletBackupLockoutDuration(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

@ -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 co.electriccoin.zcash.spackle.Twig
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.repository.BiometricRepository
import co.electriccoin.zcash.ui.common.repository.BiometricRequest
@ -25,7 +26,7 @@ import com.flexa.spend.buildSpend
class CreateFlexaTransactionUseCase(
private val getSynchronizer: GetSynchronizerUseCase,
private val getZashiAccount: GetZashiAccountUseCase,
private val getSpendingKey: GetZashiSpendingKeyUseCase,
private val zashiSpendingKeyDataSource: ZashiSpendingKeyDataSource,
private val biometricRepository: BiometricRepository,
private val context: Context,
) {
@ -42,7 +43,10 @@ class CreateFlexaTransactionUseCase(
)
}.onSuccess { proposal ->
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) {
is SubmitResult.Success -> {
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.model.WalletRestoringState
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.viewmodel.SynchronizerError
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.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
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 kotlin.time.Duration.Companion.seconds
@ -26,23 +24,23 @@ class GetHomeMessageUseCase(
private val walletRepository: WalletRepository,
private val walletBackupDataSource: WalletBackupDataSource,
private val exchangeRateRepository: ExchangeRateRepository,
private val shieldFundsRepository: ShieldFundsRepository,
) {
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
fun observe() = combine(
@OptIn(FlowPreview::class)
fun observe(): Flow<HomeMessageData?> = combine(
walletRepository.currentWalletSnapshot.filterNotNull(),
walletRepository.walletRestoringState,
walletBackupDataSource.observe(),
exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(),
) { walletSnapshot, walletStateInformation, backup, isCCAvailable ->
Quadruple(walletSnapshot, walletStateInformation, backup, isCCAvailable)
}.flatMapLatest { (walletSnapshot, walletStateInformation, backup, isCCAvailable) ->
shieldFundsRepository.availability
) { walletSnapshot, walletStateInformation, backup, isCCAvailable, shieldFunds ->
when {
walletSnapshot.synchronizerError != null -> {
flowOf(HomeMessageData.Error(walletSnapshot.synchronizerError))
HomeMessageData.Error(walletSnapshot.synchronizerError)
}
walletSnapshot.status == Synchronizer.Status.DISCONNECTED -> {
flowOf(HomeMessageData.Disconnected)
HomeMessageData.Disconnected
}
walletSnapshot.status in listOf(
@ -50,34 +48,35 @@ class GetHomeMessageUseCase(
Synchronizer.Status.SYNCING,
Synchronizer.Status.STOPPED
) -> {
flow {
val progress = walletSnapshot.progress.decimal * 100f
val result = when {
walletStateInformation == WalletRestoringState.RESTORING -> {
HomeMessageData.Restoring(
progress = progress,
)
}
else -> {
HomeMessageData.Syncing(progress = progress)
}
val progress = walletSnapshot.progress.decimal * 100f
val result = when {
walletStateInformation == WalletRestoringState.RESTORING -> {
HomeMessageData.Restoring(
progress = progress,
)
}
else -> {
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)
}
sealed interface 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 Disconnected : 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
) {
suspend operator fun invoke() {
walletBackupDataSource.remindMeLater()
walletBackupDataSource.onUserSavedWalletBackup()
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.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.provider.WalletBackupConsentStorageProvider
class RemindWalletBackupLaterUseCase(
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()
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.syncing.WalletSyncingMessage
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.transparentbalance.TransparentBalanceMessageState
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessage
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.WalletUpdatingMessageState
import kotlinx.coroutines.delay
@ -69,10 +69,11 @@ fun HomeMessage(
)
val bottomHeight by animateDpAsState(
targetValue = if (isVisible) BOTTOM_CUTOUT_HEIGHT_DP.dp else TOP_CUTOUT_HEIGHT_DP.dp,
animationSpec =
animationSpec(
delay = if (isVisible) null else 250.milliseconds
)
animationSpec = 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(
@ -85,7 +86,7 @@ fun HomeMessage(
.height(TOP_CUTOUT_HEIGHT_DP.dp)
.zIndex(2f)
.bottomOnlyShadow(
elevation = 2.dp,
elevation = elevation,
shape =
RoundedCornerShape(
bottomStart = TOP_CUTOUT_HEIGHT_DP.dp,
@ -128,10 +129,10 @@ fun HomeMessage(
contentPadding = contentPadding
)
is TransparentBalanceMessageState ->
TransparentBalanceMessage(
is ShieldFundsMessageState ->
ShieldFundsMessage(
innerModifier = innerModifier,
state = normalizedState as TransparentBalanceMessageState,
state = normalizedState as ShieldFundsMessageState,
contentPadding = contentPadding
)
@ -184,7 +185,7 @@ fun HomeMessage(
.zIndex(1f)
.align(Alignment.BottomCenter)
.topOnlyShadow(
elevation = 2.dp,
elevation = elevation,
shape = RoundedCornerShape(topStart = bottomCornerSize, topEnd = bottomCornerSize),
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 ELEVATION_ANIMATION_DURATION_MS = 650
private const val ANIMATION_DURATION_BETWEEN_MESSAGES_MS = 1000
private const val TOP_CUTOUT_HEIGHT_DP = 32
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.IsRestoreSuccessDialogVisibleUseCase
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.util.stringRes
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.currency.EnableCurrencyConversionMessageState
import co.electriccoin.zcash.ui.screen.home.transparentbalance.TransparentBalanceMessageState
import co.electriccoin.zcash.ui.screen.home.backup.WalletBackupDetail
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.WalletDisconnectedMessageState
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.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.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.WalletUpdatingMessageState
import co.electriccoin.zcash.ui.screen.integrations.DialogIntegrations
import co.electriccoin.zcash.ui.screen.receive.Receive
import co.electriccoin.zcash.ui.screen.receive.model.ReceiveAddressType
import co.electriccoin.zcash.ui.screen.scan.Scan
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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@ -55,6 +56,7 @@ class HomeViewModel(
private val navigationRouter: NavigationRouter,
private val isRestoreSuccessDialogVisible: IsRestoreSuccessDialogVisibleUseCase,
private val navigateToCoinbase: NavigateToCoinbaseUseCase,
private val shieldFunds: ShieldFundsUseCase
) : ViewModel() {
private val messageState = getHomeMessage
@ -177,10 +179,13 @@ class HomeViewModel(
onClick = ::onWalletSyncingMessageClick
)
is HomeMessageData.TransparentBalance -> TransparentBalanceMessageState(
subtitle = stringRes(zatoshi = it.zatoshi),
onClick = ::onTransparentBalanceMessageClick,
onButtonClick = ::onTransparentBalanceMessageButtonClick,
is HomeMessageData.ShieldFunds -> ShieldFundsMessageState(
subtitle = stringRes(
R.string.home_message_transparent_balance_subtitle,
stringRes(it.zatoshi)
),
onClick = ::onShieldFundsMessageClick,
onButtonClick = ::onShieldFundsMessageButtonClick,
)
HomeMessageData.Updating -> WalletUpdatingMessageState(
@ -190,73 +195,40 @@ class HomeViewModel(
null -> null
}
private fun onRestoreDialogSeenClick() =
viewModelScope.launch {
isRestoreSuccessDialogVisible.setSeen()
}
private fun onRestoreDialogSeenClick() = viewModelScope.launch { isRestoreSuccessDialogVisible.setSeen() }
private fun onMoreButtonClick() {
navigationRouter.forward(DialogIntegrations)
}
private fun onMoreButtonClick() = navigationRouter.forward(DialogIntegrations)
private fun onSendButtonClick() {
navigationRouter.forward(Send())
}
private fun onSendButtonClick() = navigationRouter.forward(Send())
private fun onReceiveButtonClick() {
navigationRouter.forward(Receive)
}
private fun onReceiveButtonClick() = navigationRouter.forward(Receive)
private fun onScanButtonClick() {
navigationRouter.forward(Scan(ScanFlow.HOMEPAGE))
}
private fun onScanButtonClick() = navigationRouter.forward(Scan(ScanFlow.HOMEPAGE))
private fun onBuyClick() =
viewModelScope.launch {
navigateToCoinbase(replaceCurrentScreen = false)
}
private fun onBuyClick() = viewModelScope.launch { navigateToCoinbase(replaceCurrentScreen = false) }
private fun onRequestClick() {
private fun onRequestClick() =
navigationRouter.forward("${NavigationTargets.REQUEST}/${ReceiveAddressType.Unified.ordinal}")
}
private fun onWalletUpdatingMessageClick() {
navigationRouter.forward(WalletUpdatingInfo)
}
private fun onWalletUpdatingMessageClick() = navigationRouter.forward(WalletUpdatingInfo)
private fun onWalletSyncingMessageClick() {
navigationRouter.forward(WalletSyncingInfo)
}
private fun onWalletSyncingMessageClick() = navigationRouter.forward(WalletSyncingInfo)
private fun onWalletRestoringMessageClick() {
navigationRouter.forward(WalletRestoringInfo)
}
private fun onWalletRestoringMessageClick() = navigationRouter.forward(WalletRestoringInfo)
private fun onEnableCurrencyConversionClick() {
navigationRouter.forward(ExchangeRateOptIn)
}
private fun onEnableCurrencyConversionClick() = navigationRouter.forward(ExchangeRateOptIn)
private fun onWalletDisconnectedMessageClick() {
navigationRouter.forward(WalletDisconnectedInfo)
}
private fun onWalletDisconnectedMessageClick() = navigationRouter.forward(WalletDisconnectedInfo)
private fun onWalletBackupMessageClick() {
navigationRouter.forward(SeedBackupInfo)
}
private fun onWalletBackupMessageClick() = navigationRouter.forward(SeedBackupInfo)
private fun onWalletBackupMessageButtonClick() {
navigationRouter.forward(WalletBackupDetail(false))
}
private fun onWalletBackupMessageButtonClick() = navigationRouter.forward(WalletBackupDetail(false))
private fun onTransparentBalanceMessageClick() {
navigationRouter.forward(TransparentBalanceInfo)
}
private fun onShieldFundsMessageClick() = navigationRouter.forward(ShieldFundsInfo)
private fun onTransparentBalanceMessageButtonClick(): Nothing {
TODO()
}
private fun onShieldFundsMessageButtonClick() = shieldFunds(navigateBackAfterSuccess = false)
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error): Nothing {
private fun onWalletErrorMessageClick(homeMessageData: HomeMessageData.Error) {
// statusText =
// context.getString(
// R.string.balances_status_error_simple,

View File

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

View File

@ -4,6 +4,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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.SheetState
import androidx.compose.material3.Text
@ -15,10 +17,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.ui.R
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.ZashiBulletText
import co.electriccoin.zcash.ui.design.component.ZashiButton
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.rememberScreenModalBottomSheetState
import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
@ -38,9 +42,11 @@ fun WalletBackupInfoView(
state = state
) {
Column(
modifier =
Modifier
.padding(horizontal = 24.dp)
modifier = Modifier
.weight(1f, false)
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp)
) {
Image(
painter = painterResource(R.drawable.ic_info_backup),
@ -84,6 +90,12 @@ fun WalletBackupInfoView(
style = ZashiTypography.textMd
)
Spacer(32.dp)
it.checkboxState?.let { checkbox ->
ZashiCheckbox(
state = checkbox
)
Spacer(12.dp)
}
ZashiButton(
modifier = Modifier.fillMaxWidth(),
state = it.secondaryButton,
@ -108,11 +120,17 @@ private fun Preview() =
onBack = {},
secondaryButton = ButtonState(
text = stringRes(R.string.general_remind_me_later),
onClick = {}
onClick = {},
isEnabled = false
),
primaryButton = ButtonState(
text = stringRes(R.string.general_ok),
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.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.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.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.CheckboxState
import co.electriccoin.zcash.ui.design.util.stringRes
import kotlinx.coroutines.flow.MutableStateFlow
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.flow.update
import kotlinx.coroutines.launch
class WalletBackupInfoViewModel(
walletBackupDataSource: WalletBackupDataSource,
walletBackupConsentStorageProvider: WalletBackupConsentStorageProvider,
private val navigationRouter: NavigationRouter,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : ViewModel() {
val state: StateFlow<WalletBackupInfoState?> = MutableStateFlow(
WalletBackupInfoState(
onBack = ::onBack,
secondaryButton = ButtonState(
text = stringRes(R.string.general_remind_me_later),
onClick = ::onRemindMeLaterClick
),
primaryButton = ButtonState(
text = stringRes(R.string.general_ok),
onClick = ::onPrimaryClick
)
private val isConsentChecked = MutableStateFlow(false)
private val lockoutDuration = walletBackupDataSource
.observe()
.filterIsInstance<WalletBackupAvailability.Available>()
.take(1)
.map { it.lockoutDuration }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null
)
)
private fun onPrimaryClick() {
navigationRouter.replace(WalletBackupDetail(true))
}
val state: StateFlow<WalletBackupInfoState?> = combine(
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()
}

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.runtime.Composable
@ -9,11 +9,11 @@ import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AndroidTransparentBalanceInfo() {
val vm = koinViewModel<TransparentBalanceInfoViewModel>()
fun AndroidShieldFundsInfo() {
val vm = koinViewModel<ShieldFundsInfoViewModel>()
val state by vm.state.collectAsStateWithLifecycle()
state?.let { TransparentBalanceInfoView(it) }
ShieldFundsInfoView(state)
}
@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 co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.ModalBottomSheetState
data class TransparentBalanceInfoState(
data class ShieldFundsInfoState(
val transparentAmount: Zatoshi,
override val onBack: () -> Unit,
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.layout.Column
@ -34,8 +34,8 @@ import co.electriccoin.zcash.ui.design.util.stringRes
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TransparentBalanceInfoView(
state: TransparentBalanceInfoState?,
fun ShieldFundsInfoView(
state: ShieldFundsInfoState?,
sheetState: SheetState = rememberScreenModalBottomSheetState(),
) {
ZashiScreenModalBottomSheet(
@ -47,7 +47,7 @@ fun TransparentBalanceInfoView(
}
@Composable
private fun Content(state: TransparentBalanceInfoState) {
private fun Content(state: ShieldFundsInfoState) {
Column(
modifier =
Modifier
@ -132,9 +132,9 @@ private fun Content(state: TransparentBalanceInfoState) {
@Composable
private fun Preview() =
ZcashTheme {
TransparentBalanceInfoView(
ShieldFundsInfoView(
state =
TransparentBalanceInfoState(
ShieldFundsInfoState(
onBack = {},
primaryButton =
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.layout.PaddingValues
@ -26,9 +26,9 @@ import co.electriccoin.zcash.ui.screen.home.HomeMessageWrapper
@Suppress("ModifierNaming")
@Composable
fun TransparentBalanceMessage(
fun ShieldFundsMessage(
contentPadding: PaddingValues,
state: TransparentBalanceMessageState,
state: ShieldFundsMessageState,
innerModifier: Modifier = Modifier,
) {
HomeMessageWrapper(
@ -65,7 +65,7 @@ fun TransparentBalanceMessage(
)
}
class TransparentBalanceMessageState(
class ShieldFundsMessageState(
val subtitle: StringResource,
val onClick: () -> Unit,
val onButtonClick: () -> Unit,
@ -76,9 +76,9 @@ class TransparentBalanceMessageState(
private fun Preview() =
ZcashTheme {
BlankSurface {
TransparentBalanceMessage(
ShieldFundsMessage(
state =
TransparentBalanceMessageState(
ShieldFundsMessageState(
subtitle =
stringRes(
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.ZashiAccount
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.design.util.stringRes
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.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class ReceiveViewModel(
observeSelectedWalletAccount: ObserveSelectedWalletAccountUseCase,
observeOnAccountChanged: ObserveOnAccountChangedUseCase,
private val application: Application,
private val copyToClipboard: CopyToClipboardUseCase,
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 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() {
val viewModel = koinViewModel<TransactionFiltersViewModel>()
val state by viewModel.state.collectAsStateWithLifecycle()
TransactionFiltersView(
state = state,
)
TransactionFiltersView(state = state)
}

View File

@ -5,6 +5,8 @@ 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.WalletBackupAvailability
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
import co.electriccoin.zcash.ui.common.usecase.ObservePersistableWalletUseCase
import co.electriccoin.zcash.ui.common.usecase.OnUserSavedWalletBackupUseCase
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.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class WalletBackupViewModel(
private val args: WalletBackup,
walletBackupDataSource: WalletBackupDataSource,
observePersistableWallet: ObservePersistableWalletUseCase,
private val args: WalletBackup,
private val navigationRouter: NavigationRouter,
private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : 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 isRemindMeLaterButtonVisible = isRevealed
@ -42,11 +59,16 @@ class WalletBackupViewModel(
combine(
isRevealed,
isRemindMeLaterButtonVisible,
observableWallet
) { isRevealed, isRemindMeLaterButtonVisible, wallet ->
observableWallet,
lockoutDuration
) { isRevealed, isRemindMeLaterButtonVisible, wallet, lockoutDuration ->
WalletBackupState(
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
).takeIf { isRemindMeLaterButtonVisible },
primaryButton =
@ -105,10 +127,7 @@ class WalletBackupViewModel(
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null)
private fun onRemindMeLaterClick() =
viewModelScope.launch {
remindWalletBackupLater()
}
private fun onRemindMeLaterClick() = viewModelScope.launch { remindWalletBackupLater(persistConsent = false) }
private fun onWalletBackupSavedClick() =
viewModelScope.launch {

View File

@ -30,4 +30,8 @@
<string name="general_ok">OK</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>

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>
<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>
<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_title">Always Shield Transparent Funds</string>
<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>
<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>
<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_title">Always Shield Transparent Funds</string>
<string name="home_info_transparent_subtitle">To protect user privacy, Zashi doesn\'t support spending transparent