Code cleanup

This commit is contained in:
Milan Cerovsky 2025-04-22 16:45:58 +02:00
parent d488daeafd
commit 0db65c410a
81 changed files with 1081 additions and 939 deletions

View File

@ -3,7 +3,9 @@ package co.electriccoin.zcash.preference.model.entry
import co.electriccoin.zcash.preference.api.PreferenceProvider import co.electriccoin.zcash.preference.api.PreferenceProvider
import java.time.Instant import java.time.Instant
class TimestampPreferenceDefault(override val key: PreferenceKey): PreferenceDefault<Instant?> { class TimestampPreferenceDefault(
override val key: PreferenceKey
) : PreferenceDefault<Instant?> {
override suspend fun getValue(preferenceProvider: PreferenceProvider) = override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getLong(key)?.let { Instant.ofEpochMilli(it) } preferenceProvider.getLong(key)?.let { Instant.ofEpochMilli(it) }

View File

@ -65,7 +65,8 @@ fun ZashiBigIconButton(
Modifier.background(darkBgGradient) Modifier.background(darkBgGradient)
Surface( Surface(
modifier = modifier modifier =
modifier
.pointerInput(Unit) { .pointerInput(Unit) {
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {

View File

@ -41,12 +41,12 @@ fun ZashiScreenDialog(
@Composable @Composable
private fun Dialog( private fun Dialog(
modifier: Modifier = Modifier,
positive: ButtonState, positive: ButtonState,
negative: ButtonState, negative: ButtonState,
onDismissRequest: (() -> Unit),
title: StringResource, title: StringResource,
message: StringResource, message: StringResource,
onDismissRequest: (() -> Unit),
modifier: Modifier = Modifier,
properties: DialogProperties = DialogProperties() properties: DialogProperties = DialogProperties()
) { ) {
AlertDialog( AlertDialog(

View File

@ -1,15 +1,15 @@
@file:Suppress("TooManyFunctions")
package co.electriccoin.zcash.ui.design.component package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
@ -84,7 +84,8 @@ fun ZashiYearMonthWheelDatePicker(
.align(Alignment.Center), .align(Alignment.Center),
) { ) {
Box( Box(
modifier = Modifier modifier =
Modifier
.weight(1f) .weight(1f)
.height(34.dp) .height(34.dp)
.padding(top = 1.dp) .padding(top = 1.dp)
@ -92,7 +93,8 @@ fun ZashiYearMonthWheelDatePicker(
) )
Spacer(36.dp) Spacer(36.dp)
Box( Box(
modifier = Modifier modifier =
Modifier
.weight(1f) .weight(1f)
.height(34.dp) .height(34.dp)
.padding(top = 1.dp) .padding(top = 1.dp)
@ -109,7 +111,8 @@ fun ZashiYearMonthWheelDatePicker(
itemVerticalOffset = verticallyVisibleItems, itemVerticalOffset = verticallyVisibleItems,
isInfiniteScroll = false, isInfiniteScroll = false,
onFocusItem = { onFocusItem = {
state = state.copy( state =
state.copy(
selectedDate = state.selectedDate.withMonth(state.months[it].value) selectedDate = state.selectedDate.withMonth(state.months[it].value)
) )
}, },
@ -133,7 +136,8 @@ fun ZashiYearMonthWheelDatePicker(
isInfiniteScroll = false, isInfiniteScroll = false,
onFocusItem = { onFocusItem = {
val year = state.years[it] val year = state.years[it]
val normalizedSelectedMonth = getSelectedMonthForYear( val normalizedSelectedMonth =
getSelectedMonthForYear(
year = year, year = year,
selectedMonth = state.selectedDate.month, selectedMonth = state.selectedDate.month,
startYearMonth = startInclusive, startYearMonth = startInclusive,
@ -141,7 +145,8 @@ fun ZashiYearMonthWheelDatePicker(
) )
val months = getMonthsForYear(year, startInclusive, endInclusive) val months = getMonthsForYear(year, startInclusive, endInclusive)
val selectedDate = state.selectedDate.withYear(year.value).withMonth(normalizedSelectedMonth.value) val selectedDate = state.selectedDate.withYear(year.value).withMonth(normalizedSelectedMonth.value)
state = state.copy( state =
state.copy(
selectedDate = selectedDate, selectedDate = selectedDate,
months = months months = months
) )
@ -161,8 +166,8 @@ fun ZashiYearMonthWheelDatePicker(
} }
} }
private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth: YearMonth): List<Month> { private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth: YearMonth): List<Month> =
return when (year.value) { when (year.value) {
startYearMonth.year -> { startYearMonth.year -> {
(startYearMonth.month.value..Month.DECEMBER.value).map { index -> (startYearMonth.month.value..Month.DECEMBER.value).map { index ->
Month.entries.first { it.value == index } Month.entries.first { it.value == index }
@ -192,24 +197,25 @@ private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth
) )
} }
} }
}
private fun getSelectedMonthForYear( private fun getSelectedMonthForYear(
year: Year, year: Year,
selectedMonth: Month, selectedMonth: Month,
startYearMonth: YearMonth, startYearMonth: YearMonth,
endYearMonth: YearMonth endYearMonth: YearMonth
): Month { ): Month =
return when (year.value) { when (year.value) {
startYearMonth.year -> { startYearMonth.year -> {
val months = (startYearMonth.month.value..Month.DECEMBER.value).map { index -> val months =
(startYearMonth.month.value..Month.DECEMBER.value).map { index ->
Month.entries.first { it.value == index } Month.entries.first { it.value == index }
} }
if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth) if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth)
} }
endYearMonth.year -> { endYearMonth.year -> {
val months = (Month.JANUARY.value..endYearMonth.month.value).map { index -> val months =
(Month.JANUARY.value..endYearMonth.month.value).map { index ->
Month.entries.first { it.value == index } Month.entries.first { it.value == index }
} }
if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth) if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth)
@ -217,7 +223,6 @@ private fun getSelectedMonthForYear(
else -> selectedMonth else -> selectedMonth
} }
}
private fun List<Month>.findClosest(target: Month): Month { private fun List<Month>.findClosest(target: Month): Month {
var closestNumber = this[0] // Initialize with the first element var closestNumber = this[0] // Initialize with the first element
@ -400,7 +405,8 @@ private data class InternalState(
@PreviewScreens @PreviewScreens
@Composable @Composable
private fun Preview() = ZcashTheme { private fun Preview() =
ZcashTheme {
BlankSurface { BlankSurface {
ZashiYearMonthWheelDatePicker( ZashiYearMonthWheelDatePicker(
selection = YearMonth.now(), selection = YearMonth.now(),

View File

@ -8,11 +8,15 @@ import androidx.compose.runtime.Stable
sealed interface ImageResource { sealed interface ImageResource {
@Immutable @Immutable
@JvmInline @JvmInline
value class ByDrawable(@DrawableRes val resource: Int) : ImageResource value class ByDrawable(
@DrawableRes val resource: Int
) : ImageResource
@JvmInline @JvmInline
@Immutable @Immutable
value class DisplayString(val value: String) : ImageResource value class DisplayString(
val value: String
) : ImageResource
@Immutable @Immutable
data object Loading : ImageResource data object Loading : ImageResource

View File

@ -64,7 +64,9 @@ sealed interface StringResource {
} }
@Immutable @Immutable
private data class CompositeStringResource(val resources: List<StringResource>): StringResource private data class CompositeStringResource(
val resources: List<StringResource>
) : StringResource
@Stable @Stable
fun stringRes( fun stringRes(
@ -116,7 +118,8 @@ fun StringResource.getValue(
convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth, convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth,
convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress, convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress,
convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId
): String = getString( ): String =
getString(
context = LocalContext.current, context = LocalContext.current,
convertZatoshi = convertZatoshi, convertZatoshi = convertZatoshi,
convertDateTime = convertDateTime, convertDateTime = convertDateTime,
@ -133,7 +136,8 @@ fun StringResource.getString(
convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth, convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth,
convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress, convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress,
convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId
): String = when (this) { ): String =
when (this) {
is StringResource.ByResource -> context.getString(resource, *args.normalize(context).toTypedArray()) is StringResource.ByResource -> context.getString(resource, *args.normalize(context).toTypedArray())
is StringResource.ByString -> value is StringResource.ByString -> value
is StringResource.ByZatoshi -> convertZatoshi(zatoshi) is StringResource.ByZatoshi -> convertZatoshi(zatoshi)
@ -141,7 +145,8 @@ fun StringResource.getString(
is StringResource.ByYearMonth -> convertYearMonth(yearMonth) is StringResource.ByYearMonth -> convertYearMonth(yearMonth)
is StringResource.ByAddress -> convertAddress(this) is StringResource.ByAddress -> convertAddress(this)
is StringResource.ByTransactionId -> convertTransactionId(this) is StringResource.ByTransactionId -> convertTransactionId(this)
is CompositeStringResource -> this.resources.joinToString(separator = "") { is CompositeStringResource ->
this.resources.joinToString(separator = "") {
it.getString( it.getString(
context = context, context = context,
convertZatoshi = convertZatoshi, convertZatoshi = convertZatoshi,

View File

@ -37,7 +37,6 @@ import co.electriccoin.zcash.ui.screen.scan.Scan
import co.electriccoin.zcash.ui.screen.scan.viewmodel.ScanViewModel import co.electriccoin.zcash.ui.screen.scan.viewmodel.ScanViewModel
import co.electriccoin.zcash.ui.screen.scankeystone.viewmodel.ScanKeystonePCZTViewModel import co.electriccoin.zcash.ui.screen.scankeystone.viewmodel.ScanKeystonePCZTViewModel
import co.electriccoin.zcash.ui.screen.scankeystone.viewmodel.ScanKeystoneSignInRequestViewModel import co.electriccoin.zcash.ui.screen.scankeystone.viewmodel.ScanKeystoneSignInRequestViewModel
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
@ -54,6 +53,7 @@ import co.electriccoin.zcash.ui.screen.transactionhistory.widget.TransactionHist
import co.electriccoin.zcash.ui.screen.transactionnote.TransactionNote import co.electriccoin.zcash.ui.screen.transactionnote.TransactionNote
import co.electriccoin.zcash.ui.screen.transactionnote.viewmodel.TransactionNoteViewModel import co.electriccoin.zcash.ui.screen.transactionnote.viewmodel.TransactionNoteViewModel
import co.electriccoin.zcash.ui.screen.transactionprogress.TransactionProgressViewModel import co.electriccoin.zcash.ui.screen.transactionprogress.TransactionProgressViewModel
import co.electriccoin.zcash.ui.screen.walletbackup.WalletBackupViewModel
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
import co.electriccoin.zcash.ui.screen.whatsnew.viewmodel.WhatsNewViewModel import co.electriccoin.zcash.ui.screen.whatsnew.viewmodel.WhatsNewViewModel
import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModel

View File

@ -16,18 +16,21 @@ import kotlinx.coroutines.flow.update
interface MessageAvailabilityDataSource { interface MessageAvailabilityDataSource {
val canShowMessage: Flow<Boolean> val canShowMessage: Flow<Boolean>
val canShowShieldMessage: Flow<Boolean> val canShowShieldMessage: Flow<Boolean>
fun onMessageShown() fun onMessageShown()
fun onThirdPartyUiShown() fun onThirdPartyUiShown()
fun onShieldingInitiated() fun onShieldingInitiated()
} }
class MessageAvailabilityDataSourceImpl( class MessageAvailabilityDataSourceImpl(
applicationStateProvider: ApplicationStateProvider applicationStateProvider: ApplicationStateProvider
) : MessageAvailabilityDataSource { ) : MessageAvailabilityDataSource {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val state = MutableStateFlow( private val state =
MutableStateFlow(
MessageAvailabilityData( MessageAvailabilityData(
isAppInForeground = true, isAppInForeground = true,
isThirdPartyUiShown = false, isThirdPartyUiShown = false,
@ -58,8 +61,7 @@ class MessageAvailabilityDataSourceImpl(
) )
} }
} }
} }.launchIn(scope)
.launchIn(scope)
} }
override fun onMessageShown() { override fun onMessageShown() {

View File

@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.flowOf
import java.time.Instant import java.time.Instant
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
interface ShieldFundsDataSource { interface ShieldFundsDataSource {
suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability>
@ -27,9 +26,9 @@ class ShieldFundsDataSourceImpl(
private val shieldFundsRemindMeCountStorageProvider: ShieldFundsRemindMeCountStorageProvider, private val shieldFundsRemindMeCountStorageProvider: ShieldFundsRemindMeCountStorageProvider,
private val shieldFundsRemindMeTimestampStorageProvider: ShieldFundsRemindMeTimestampStorageProvider private val shieldFundsRemindMeTimestampStorageProvider: ShieldFundsRemindMeTimestampStorageProvider
) : ShieldFundsDataSource { ) : ShieldFundsDataSource {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> = combine( override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> =
combine(
shieldFundsRemindMeCountStorageProvider.observe(forAccount), shieldFundsRemindMeCountStorageProvider.observe(forAccount),
shieldFundsRemindMeTimestampStorageProvider.observe(forAccount) shieldFundsRemindMeTimestampStorageProvider.observe(forAccount)
) { count, timestamp -> ) { count, timestamp ->
@ -37,15 +36,18 @@ class ShieldFundsDataSourceImpl(
}.flatMapLatest { (count, timestamp) -> }.flatMapLatest { (count, timestamp) ->
when { when {
timestamp == null -> flowOf(ShieldFundsAvailability.Available(ShieldFundsLockoutDuration.TWO_DAYS)) timestamp == null -> flowOf(ShieldFundsAvailability.Available(ShieldFundsLockoutDuration.TWO_DAYS))
count == 1 -> calculateNext( count == 1 ->
calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = ShieldFundsLockoutDuration.TWO_DAYS, lastLockoutDuration = ShieldFundsLockoutDuration.TWO_DAYS,
nextLockoutDuration = ShieldFundsLockoutDuration.TWO_WEEKS nextLockoutDuration = ShieldFundsLockoutDuration.TWO_WEEKS
) )
else -> calculateNext( else ->
calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = if (count == 2) { lastLockoutDuration =
if (count == 2) {
ShieldFundsLockoutDuration.TWO_WEEKS ShieldFundsLockoutDuration.TWO_WEEKS
} else { } else {
ShieldFundsLockoutDuration.ONE_MONTH ShieldFundsLockoutDuration.ONE_MONTH
@ -83,11 +85,17 @@ class ShieldFundsDataSourceImpl(
} }
sealed interface ShieldFundsAvailability { sealed interface ShieldFundsAvailability {
data class Available(val lockoutDuration: ShieldFundsLockoutDuration) : ShieldFundsAvailability data class Available(
val lockoutDuration: ShieldFundsLockoutDuration
) : ShieldFundsAvailability
data object Unavailable : ShieldFundsAvailability data object Unavailable : ShieldFundsAvailability
} }
enum class ShieldFundsLockoutDuration(val duration: Duration, @StringRes val res: Int) { enum class ShieldFundsLockoutDuration(
val duration: Duration,
@StringRes val res: Int
) {
TWO_DAYS(2.days, R.string.general_remind_me_in_two_days), TWO_DAYS(2.days, R.string.general_remind_me_in_two_days),
TWO_WEEKS(2.days, R.string.general_remind_me_in_two_weeks), TWO_WEEKS(2.days, R.string.general_remind_me_in_two_weeks),
ONE_MONTH(30.days, R.string.general_remind_me_in_two_months) ONE_MONTH(30.days, R.string.general_remind_me_in_two_months)

View File

@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.flowOf
import java.time.Instant import java.time.Instant
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
interface WalletBackupDataSource { interface WalletBackupDataSource {
fun observe(): Flow<WalletBackupAvailability> fun observe(): Flow<WalletBackupAvailability>
@ -30,9 +29,9 @@ class WalletBackupDataSourceImpl(
private val walletBackupRemindMeCountStorageProvider: WalletBackupRemindMeCountStorageProvider, private val walletBackupRemindMeCountStorageProvider: WalletBackupRemindMeCountStorageProvider,
private val walletBackupRemindMeTimestampStorageProvider: WalletBackupRemindMeTimestampStorageProvider private val walletBackupRemindMeTimestampStorageProvider: WalletBackupRemindMeTimestampStorageProvider
) : WalletBackupDataSource { ) : WalletBackupDataSource {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override fun observe(): Flow<WalletBackupAvailability> = combine( override fun observe(): Flow<WalletBackupAvailability> =
combine(
walletBackupFlagStorageProvider.observe(), walletBackupFlagStorageProvider.observe(),
walletBackupRemindMeCountStorageProvider.observe(), walletBackupRemindMeCountStorageProvider.observe(),
walletBackupRemindMeTimestampStorageProvider.observe() walletBackupRemindMeTimestampStorageProvider.observe()
@ -42,15 +41,18 @@ class WalletBackupDataSourceImpl(
when { when {
isBackedUp -> flowOf(WalletBackupAvailability.Unavailable) isBackedUp -> flowOf(WalletBackupAvailability.Unavailable)
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS)) timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS))
count == 1 -> calculateNext( count == 1 ->
calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS, lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS,
nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS
) )
else -> calculateNext( else ->
calculateNext(
lastTimestamp = timestamp, lastTimestamp = timestamp,
lastLockoutDuration = if (count == 2) { lastLockoutDuration =
if (count == 2) {
WalletBackupLockoutDuration.TWO_WEEKS WalletBackupLockoutDuration.TWO_WEEKS
} else { } else {
WalletBackupLockoutDuration.ONE_MONTH WalletBackupLockoutDuration.ONE_MONTH
@ -92,11 +94,17 @@ class WalletBackupDataSourceImpl(
} }
sealed interface WalletBackupAvailability { sealed interface WalletBackupAvailability {
data class Available(val lockoutDuration: WalletBackupLockoutDuration) : WalletBackupAvailability data class Available(
val lockoutDuration: WalletBackupLockoutDuration
) : WalletBackupAvailability
data object Unavailable : WalletBackupAvailability data object Unavailable : WalletBackupAvailability
} }
enum class WalletBackupLockoutDuration(val duration: Duration, @StringRes val res: Int) { enum class WalletBackupLockoutDuration(
val duration: Duration,
@StringRes val res: Int
) {
TWO_DAYS(2.days, R.string.general_remind_me_in_two_days), TWO_DAYS(2.days, R.string.general_remind_me_in_two_days),
TWO_WEEKS(14.days, R.string.general_remind_me_in_two_weeks), TWO_WEEKS(14.days, R.string.general_remind_me_in_two_weeks),
ONE_MONTH(30.days, R.string.general_remind_me_in_two_months), ONE_MONTH(30.days, R.string.general_remind_me_in_two_months),

View File

@ -30,11 +30,11 @@ class WalletSnapshotDataSourceImpl(
synchronizerProvider: SynchronizerProvider, synchronizerProvider: SynchronizerProvider,
walletRestoringStateProvider: WalletRestoringStateProvider, walletRestoringStateProvider: WalletRestoringStateProvider,
) : WalletSnapshotDataSource { ) : WalletSnapshotDataSource {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
val flow = synchronizerProvider val flow =
synchronizerProvider
.synchronizer .synchronizer
.flatMapLatest { synchronizer -> .flatMapLatest { synchronizer ->
if (synchronizer == null) { if (synchronizer == null) {
@ -56,8 +56,7 @@ class WalletSnapshotDataSourceImpl(
) )
} }
} }
} }.stateIn(
.stateIn(
scope = scope, scope = scope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null initialValue = null

View File

@ -53,17 +53,10 @@ sealed interface WalletAccount : Comparable<WalletAccount> {
val isShieldedPending: Boolean val isShieldedPending: Boolean
get() = pendingShieldedBalance > Zatoshi(0) get() = pendingShieldedBalance > Zatoshi(0)
@Suppress("MagicNumber")
val isShieldingAvailable: Boolean val isShieldingAvailable: Boolean
get() = totalTransparentBalance > Zatoshi(100000L) get() = totalTransparentBalance > Zatoshi(100000L)
val isProcessingZeroSpendableBalance: Boolean
get() {
if (totalShieldedBalance == Zatoshi(0) && totalTransparentBalance > Zatoshi(0)) {
return false
}
return totalBalance > Zatoshi(0) && totalShieldedBalance == Zatoshi(0)
}
fun canSpend(amount: Zatoshi): Boolean = spendableShieldedBalance >= amount fun canSpend(amount: Zatoshi): Boolean = spendableShieldedBalance >= amount
} }

View File

@ -1,10 +1,7 @@
package co.electriccoin.zcash.ui.common.model package co.electriccoin.zcash.ui.common.model
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
// TODO [#292]: Should be moved to SDK-EXT-UI module. // TODO [#292]: Should be moved to SDK-EXT-UI module.

View File

@ -5,6 +5,9 @@ import co.electriccoin.zcash.preference.model.entry.PreferenceKey
interface BooleanStorageProvider : StorageProvider<Boolean> interface BooleanStorageProvider : StorageProvider<Boolean>
abstract class BaseBooleanStorageProvider(key: PreferenceKey) : BaseStorageProvider<Boolean>(), BooleanStorageProvider { abstract class BaseBooleanStorageProvider(
key: PreferenceKey
) : BaseStorageProvider<Boolean>(),
BooleanStorageProvider {
override val default = BooleanPreferenceDefault(key = key, defaultValue = false) override val default = BooleanPreferenceDefault(key = key, defaultValue = false)
} }

View File

@ -5,6 +5,9 @@ import co.electriccoin.zcash.preference.model.entry.PreferenceKey
interface IntStorageProvider : StorageProvider<Int> interface IntStorageProvider : StorageProvider<Int>
abstract class BaseIntStorageProvider(key: PreferenceKey) : IntStorageProvider, BaseStorageProvider<Int>() { abstract class BaseIntStorageProvider(
key: PreferenceKey
) : BaseStorageProvider<Int>(),
IntStorageProvider {
override val default = IntegerPreferenceDefault(key = key, defaultValue = 0) override val default = IntegerPreferenceDefault(key = key, defaultValue = 0)
} }

View File

@ -7,4 +7,5 @@ interface RestoreTimestampStorageProvider : TimestampStorageProvider
class RestoreTimestampStorageProviderImpl( class RestoreTimestampStorageProviderImpl(
override val preferenceHolder: EncryptedPreferenceProvider override val preferenceHolder: EncryptedPreferenceProvider
) : BaseTimestampStorageProvider(PreferenceKey("restore_timestamp")), RestoreTimestampStorageProvider ) : BaseTimestampStorageProvider(PreferenceKey("restore_timestamp")),
RestoreTimestampStorageProvider

View File

@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui.common.provider
import cash.z.ecc.android.sdk.model.AccountUuid import cash.z.ecc.android.sdk.model.AccountUuid
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.api.PreferenceProvider
import co.electriccoin.zcash.preference.model.entry.IntegerPreferenceDefault import co.electriccoin.zcash.preference.model.entry.IntegerPreferenceDefault
import co.electriccoin.zcash.preference.model.entry.PreferenceKey import co.electriccoin.zcash.preference.model.entry.PreferenceKey
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -18,22 +17,21 @@ interface ShieldFundsRemindMeCountStorageProvider {
class ShieldFundsRemindMeCountStorageProviderImpl( class ShieldFundsRemindMeCountStorageProviderImpl(
private val encryptedPreferenceProvider: EncryptedPreferenceProvider private val encryptedPreferenceProvider: EncryptedPreferenceProvider
) : ShieldFundsRemindMeCountStorageProvider { ) : ShieldFundsRemindMeCountStorageProvider {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
private fun getDefault(forAccount: AccountUuid): IntegerPreferenceDefault { private fun getDefault(forAccount: AccountUuid): IntegerPreferenceDefault {
val key = PreferenceKey("shield_funds_remind_me_count_${forAccount.value.toHexString()}") val key = PreferenceKey("shield_funds_remind_me_count_${forAccount.value.toHexString()}")
return IntegerPreferenceDefault(key = key, defaultValue = 0) return IntegerPreferenceDefault(key = key, defaultValue = 0)
} }
override suspend fun get(forAccount: AccountUuid): Int { override suspend fun get(forAccount: AccountUuid): Int =
return getDefault(forAccount).getValue(encryptedPreferenceProvider()) getDefault(forAccount)
} .getValue(encryptedPreferenceProvider())
override suspend fun store(forAccount: AccountUuid, amount: Int) { override suspend fun store(forAccount: AccountUuid, amount: Int) {
getDefault(forAccount).putValue(encryptedPreferenceProvider(), amount) getDefault(forAccount).putValue(encryptedPreferenceProvider(), amount)
} }
override suspend fun observe(forAccount: AccountUuid): Flow<Int> { override suspend fun observe(forAccount: AccountUuid): Flow<Int> =
return getDefault(forAccount).observe(encryptedPreferenceProvider()) getDefault(forAccount)
} .observe(encryptedPreferenceProvider())
} }

View File

@ -2,8 +2,8 @@ package co.electriccoin.zcash.ui.common.provider
import cash.z.ecc.android.sdk.model.AccountUuid import cash.z.ecc.android.sdk.model.AccountUuid
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.model.entry.TimestampPreferenceDefault
import co.electriccoin.zcash.preference.model.entry.PreferenceKey import co.electriccoin.zcash.preference.model.entry.PreferenceKey
import co.electriccoin.zcash.preference.model.entry.TimestampPreferenceDefault
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.time.Instant import java.time.Instant
@ -24,15 +24,13 @@ class ShieldFundsRemindMeTimestampStorageProviderImpl(
return TimestampPreferenceDefault(key = key) return TimestampPreferenceDefault(key = key)
} }
override suspend fun get(forAccount: AccountUuid): Instant? { override suspend fun get(forAccount: AccountUuid) = getDefault(forAccount).getValue(encryptedPreferenceProvider())
return getDefault(forAccount).getValue(encryptedPreferenceProvider())
}
override suspend fun store(forAccount: AccountUuid, timestamp: Instant) { override suspend fun store(forAccount: AccountUuid, timestamp: Instant) {
getDefault(forAccount).putValue(encryptedPreferenceProvider(), timestamp) getDefault(forAccount).putValue(encryptedPreferenceProvider(), timestamp)
} }
override suspend fun observe(forAccount: AccountUuid): Flow<Instant?> { override suspend fun observe(forAccount: AccountUuid): Flow<Instant?> =
return getDefault(forAccount).observe(encryptedPreferenceProvider()) getDefault(forAccount)
} .observe(encryptedPreferenceProvider())
} }

View File

@ -58,4 +58,3 @@ abstract class BaseNullableStorageProvider<T : Any> : NullableStorageProvider<T>
private suspend fun getPreferenceProvider(): PreferenceProvider = preferenceHolder() private suspend fun getPreferenceProvider(): PreferenceProvider = preferenceHolder()
} }

View File

@ -10,7 +10,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
@ -18,7 +17,6 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.time.Duration
interface SynchronizerProvider { interface SynchronizerProvider {
val synchronizer: StateFlow<Synchronizer?> val synchronizer: StateFlow<Synchronizer?>

View File

@ -8,6 +8,7 @@ interface TimestampStorageProvider : NullableStorageProvider<Instant>
abstract class BaseTimestampStorageProvider( abstract class BaseTimestampStorageProvider(
key: PreferenceKey key: PreferenceKey
) : BaseNullableStorageProvider<Instant>(), TimestampStorageProvider { ) : BaseNullableStorageProvider<Instant>(),
TimestampStorageProvider {
override val default = TimestampPreferenceDefault(key) override val default = TimestampPreferenceDefault(key)
} }

View File

@ -7,4 +7,5 @@ interface WalletBackupConsentStorageProvider : BooleanStorageProvider
class WalletBackupConsentStorageProviderImpl( class WalletBackupConsentStorageProviderImpl(
override val preferenceHolder: EncryptedPreferenceProvider override val preferenceHolder: EncryptedPreferenceProvider
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_consent")), WalletBackupConsentStorageProvider ) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_consent")),
WalletBackupConsentStorageProvider

View File

@ -7,4 +7,5 @@ interface WalletBackupFlagStorageProvider : BooleanStorageProvider
class WalletBackupFlagStorageProviderImpl( class WalletBackupFlagStorageProviderImpl(
override val preferenceHolder: EncryptedPreferenceProvider override val preferenceHolder: EncryptedPreferenceProvider
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_flag")), WalletBackupFlagStorageProvider ) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_flag")),
WalletBackupFlagStorageProvider

View File

@ -11,12 +11,12 @@ interface WalletRestoringStateProvider : StorageProvider<WalletRestoringState>
class WalletRestoringStateProviderImpl( class WalletRestoringStateProviderImpl(
override val preferenceHolder: StandardPreferenceProvider, override val preferenceHolder: StandardPreferenceProvider,
) : BaseStorageProvider<WalletRestoringState>(), WalletRestoringStateProvider { ) : BaseStorageProvider<WalletRestoringState>(),
WalletRestoringStateProvider {
override val default: PreferenceDefault<WalletRestoringState> = WalletRestoringStatePreferenceDefault() override val default: PreferenceDefault<WalletRestoringState> = WalletRestoringStatePreferenceDefault()
} }
private class WalletRestoringStatePreferenceDefault : PreferenceDefault<WalletRestoringState> { private class WalletRestoringStatePreferenceDefault : PreferenceDefault<WalletRestoringState> {
private val internal = StandardPreferenceKeys.WALLET_RESTORING_STATE private val internal = StandardPreferenceKeys.WALLET_RESTORING_STATE
override val key: PreferenceKey = internal.key override val key: PreferenceKey = internal.key

View File

@ -44,7 +44,6 @@ class ExchangeRateRepositoryImpl(
private val synchronizerProvider: SynchronizerProvider, private val synchronizerProvider: SynchronizerProvider,
private val standardPreferenceProvider: StandardPreferenceProvider, private val standardPreferenceProvider: StandardPreferenceProvider,
) : ExchangeRateRepository { ) : ExchangeRateRepository {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val isExchangeRateUsdOptedIn = nullableBooleanStateFlow(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN) private val isExchangeRateUsdOptedIn = nullableBooleanStateFlow(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN)

View File

@ -28,7 +28,6 @@ interface HomeMessageCacheRepository {
class HomeMessageCacheRepositoryImpl( class HomeMessageCacheRepositoryImpl(
private val messageAvailabilityDataSource: MessageAvailabilityDataSource private val messageAvailabilityDataSource: MessageAvailabilityDataSource
) : HomeMessageCacheRepository { ) : HomeMessageCacheRepository {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
override var lastShownMessage: HomeMessageData? = null override var lastShownMessage: HomeMessageData? = null
@ -42,8 +41,7 @@ class HomeMessageCacheRepositoryImpl(
lastShownMessage = null lastShownMessage = null
lastMessage = null lastMessage = null
} }
} }.launchIn(scope)
.launchIn(scope)
} }
override fun reset() { override fun reset() {
@ -52,21 +50,33 @@ class HomeMessageCacheRepositoryImpl(
} }
} }
@Suppress("MagicNumber")
sealed interface HomeMessageData { sealed interface HomeMessageData {
val priority: Int val priority: Int
data class Error(val synchronizerError: SynchronizerError) : RuntimeMessage() data class Error(
val synchronizerError: SynchronizerError
) : RuntimeMessage()
data object Disconnected : RuntimeMessage() data object Disconnected : RuntimeMessage()
data class Restoring(val progress: Float) : RuntimeMessage()
data class Syncing(val progress: Float) : RuntimeMessage() data class Restoring(
val progress: Float
) : RuntimeMessage()
data class Syncing(
val progress: Float
) : RuntimeMessage()
data object Updating : RuntimeMessage() data object Updating : RuntimeMessage()
data object Backup : Prioritized { data object Backup : Prioritized {
override val priority: Int = 4 override val priority: Int = 4
} }
data class ShieldFunds(val zatoshi: Zatoshi) : Prioritized { data class ShieldFunds(
val zatoshi: Zatoshi
) : Prioritized {
override val priority: Int = 3 override val priority: Int = 3
} }
@ -82,6 +92,7 @@ sealed interface HomeMessageData {
/** /**
* Message which always is shown. * Message which always is shown.
*/ */
@Suppress("MagicNumber")
sealed class RuntimeMessage : HomeMessageData { sealed class RuntimeMessage : HomeMessageData {
override val priority: Int = 5 override val priority: Int = 5
} }

View File

@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
interface ShieldFundsRepository { interface ShieldFundsRepository {
val availability: Flow<ShieldFundsData> val availability: Flow<ShieldFundsData>
@ -25,7 +24,8 @@ class ShieldFundsRepositoryImpl(
private val messageAvailabilityDataSource: MessageAvailabilityDataSource, private val messageAvailabilityDataSource: MessageAvailabilityDataSource,
) : ShieldFundsRepository { ) : ShieldFundsRepository {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override val availability: Flow<ShieldFundsData> = accountDataSource override val availability: Flow<ShieldFundsData> =
accountDataSource
.selectedAccount .selectedAccount
.flatMapLatest { account -> .flatMapLatest { account ->
when { when {
@ -38,7 +38,8 @@ class ShieldFundsRepositoryImpl(
) { canShowShieldMessage, availability -> ) { canShowShieldMessage, availability ->
when { when {
!canShowShieldMessage -> ShieldFundsData.Unavailable !canShowShieldMessage -> ShieldFundsData.Unavailable
availability is ShieldFundsAvailability.Available -> ShieldFundsData.Available( availability is ShieldFundsAvailability.Available ->
ShieldFundsData.Available(
lockoutDuration = availability.lockoutDuration, lockoutDuration = availability.lockoutDuration,
amount = account.transparent.balance amount = account.transparent.balance
) )

View File

@ -73,19 +73,21 @@ class TransactionRepositoryImpl(
) )
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override val currentTransactions: Flow<List<Transaction>?> = accountDataSource.selectedAccount override val currentTransactions: Flow<List<Transaction>?> =
accountDataSource.selectedAccount
.distinctUntilChangedBy { it?.sdkAccount?.accountUuid } .distinctUntilChangedBy { it?.sdkAccount?.accountUuid }
.flatMapLatest { selected -> .flatMapLatest { selected ->
if (selected is ZashiAccount) { if (selected is ZashiAccount) {
zashiTransactions zashiTransactions
} else { } else {
observeTransactions( observeTransactions(
accountFlow = accountDataSource.selectedAccount.map { it?.sdkAccount?.accountUuid } accountFlow =
accountDataSource.selectedAccount
.map { it?.sdkAccount?.accountUuid }
.distinctUntilChanged() .distinctUntilChanged()
) )
} }
} }.stateIn(
.stateIn(
scope = scope, scope = scope,
started = SharingStarted.WhileSubscribed(5.seconds, Duration.ZERO), started = SharingStarted.WhileSubscribed(5.seconds, Duration.ZERO),
initialValue = null initialValue = null
@ -123,6 +125,7 @@ class TransactionRepositoryImpl(
} }
} }
@Suppress("CyclomaticComplexMethod")
private suspend fun createTransactions( private suspend fun createTransactions(
transactions: List<TransactionOverview>, transactions: List<TransactionOverview>,
synchronizer: Synchronizer synchronizer: Synchronizer

View File

@ -6,7 +6,6 @@ import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletInitMode import cash.z.ecc.android.sdk.WalletInitMode
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FastestServersResult import cash.z.ecc.android.sdk.model.FastestServersResult
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.PersistableWallet import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.SeedPhrase import cash.z.ecc.android.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -14,7 +13,6 @@ import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import co.electriccoin.zcash.preference.EncryptedPreferenceProvider import co.electriccoin.zcash.preference.EncryptedPreferenceProvider
import co.electriccoin.zcash.preference.StandardPreferenceProvider import co.electriccoin.zcash.preference.StandardPreferenceProvider
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.datasource.AccountDataSource import co.electriccoin.zcash.ui.common.datasource.AccountDataSource
import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource import co.electriccoin.zcash.ui.common.datasource.RestoreTimestampDataSource
import co.electriccoin.zcash.ui.common.datasource.WalletSnapshotDataSource import co.electriccoin.zcash.ui.common.datasource.WalletSnapshotDataSource
@ -28,7 +26,6 @@ import co.electriccoin.zcash.ui.common.provider.PersistableWalletProvider
import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider import co.electriccoin.zcash.ui.common.provider.SynchronizerProvider
import co.electriccoin.zcash.ui.common.provider.WalletRestoringStateProvider import co.electriccoin.zcash.ui.common.provider.WalletRestoringStateProvider
import co.electriccoin.zcash.ui.common.viewmodel.SecretState import co.electriccoin.zcash.ui.common.viewmodel.SecretState
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault import co.electriccoin.zcash.ui.preference.PersistableWalletPreferenceDefault
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import co.electriccoin.zcash.ui.screen.chooseserver.AvailableServerProvider import co.electriccoin.zcash.ui.screen.chooseserver.AvailableServerProvider
@ -36,13 +33,11 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow 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.WhileSubscribed import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.emitAll
@ -51,7 +46,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -196,7 +190,8 @@ class WalletRepositoryImpl(
/** /**
* A flow of the wallet block synchronization state. * A flow of the wallet block synchronization state.
*/ */
override val walletRestoringState: StateFlow<WalletRestoringState> = walletRestoringStateProvider override val walletRestoringState: StateFlow<WalletRestoringState> =
walletRestoringStateProvider
.observe() .observe()
.stateIn( .stateIn(
scope = scope, scope = scope,

View File

@ -23,7 +23,6 @@ class WalletSnapshotRepositoryImpl(
private val synchronizerProvider: SynchronizerProvider, private val synchronizerProvider: SynchronizerProvider,
private val walletRestoringStateProvider: WalletRestoringStateProvider private val walletRestoringStateProvider: WalletRestoringStateProvider
) : WalletSnapshotRepository { ) : WalletSnapshotRepository {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@ -38,8 +37,7 @@ class WalletSnapshotRepositoryImpl(
status to restoringState status to restoringState
} }
} }
} }.collect { (status, restoringState) ->
.collect { (status, restoringState) ->
// Once the wallet is fully synced and still in restoring state, persist the new state // Once the wallet is fully synced and still in restoring state, persist the new state
if (status == Synchronizer.Status.SYNCED && restoringState in listOf(RESTORING, NONE)) { if (status == Synchronizer.Status.SYNCED && restoringState in listOf(RESTORING, NONE)) {
walletRestoringStateProvider.store(WalletRestoringState.SYNCING) walletRestoringStateProvider.store(WalletRestoringState.SYNCING)

View File

@ -43,7 +43,8 @@ class CreateFlexaTransactionUseCase(
) )
}.onSuccess { proposal -> }.onSuccess { proposal ->
Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" } Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
val result = submitTransactions( val result =
submitTransactions(
proposal = proposal, proposal = proposal,
spendingKey = zashiSpendingKeyDataSource.getZashiSpendingKey() spendingKey = zashiSpendingKeyDataSource.getZashiSpendingKey()
) )

View File

@ -38,36 +38,44 @@ class GetHomeMessageUseCase(
private val messageAvailabilityDataSource: MessageAvailabilityDataSource, private val messageAvailabilityDataSource: MessageAvailabilityDataSource,
private val cache: HomeMessageCacheRepository, private val cache: HomeMessageCacheRepository,
) { ) {
private val backupFlow = combine( private val backupFlow =
combine(
transactionRepository.zashiTransactions, transactionRepository.zashiTransactions,
walletBackupDataSource.observe() walletBackupDataSource.observe()
) { transactions, backup -> ) { transactions, backup ->
if (backup is WalletBackupAvailability.Available && transactions.orEmpty().any { it is ReceiveTransaction }) { if (backup is WalletBackupAvailability.Available &&
transactions.orEmpty().any { it is ReceiveTransaction }
) {
backup backup
} else { } else {
WalletBackupAvailability.Unavailable WalletBackupAvailability.Unavailable
} }
}.distinctUntilChanged() }.distinctUntilChanged()
private val runtimeMessage = channelFlow { @Suppress("MagicNumber")
private val runtimeMessage =
channelFlow {
var firstSyncing: WalletSnapshot? = null var firstSyncing: WalletSnapshot? = null
launch { launch {
walletSnapshotDataSource walletSnapshotDataSource
.observe() .observe()
.filterNotNull() .filterNotNull()
.collect { walletSnapshot -> .collect { walletSnapshot ->
val result = when { val result =
when {
walletSnapshot.synchronizerError != null -> walletSnapshot.synchronizerError != null ->
HomeMessageData.Error(walletSnapshot.synchronizerError) HomeMessageData.Error(walletSnapshot.synchronizerError)
walletSnapshot.status == Synchronizer.Status.DISCONNECTED -> walletSnapshot.status == Synchronizer.Status.DISCONNECTED ->
HomeMessageData.Disconnected HomeMessageData.Disconnected
walletSnapshot.status in listOf( walletSnapshot.status in
listOf(
Synchronizer.Status.INITIALIZING, Synchronizer.Status.INITIALIZING,
Synchronizer.Status.SYNCING, Synchronizer.Status.SYNCING,
Synchronizer.Status.STOPPED Synchronizer.Status.STOPPED
) -> { )
-> {
val progress = walletSnapshot.progress.decimal * 100f val progress = walletSnapshot.progress.decimal * 100f
if (walletSnapshot.restoringState == WalletRestoringState.RESTORING) { if (walletSnapshot.restoringState == WalletRestoringState.RESTORING) {
HomeMessageData.Restoring(progress = progress) HomeMessageData.Restoring(progress = progress)
@ -102,7 +110,8 @@ class GetHomeMessageUseCase(
} }
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
private val flow = combine( private val flow =
combine(
runtimeMessage, runtimeMessage,
backupFlow, backupFlow,
exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(), exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(),
@ -133,11 +142,13 @@ class GetHomeMessageUseCase(
val someMessageBeenShown = cache.lastShownMessage != null // has any message been shown while app in fg val someMessageBeenShown = cache.lastShownMessage != null // has any message been shown while app in fg
val hasNoMessageBeenShownLately = cache.lastMessage == null // has no message been shown val hasNoMessageBeenShownLately = cache.lastMessage == null // has no message been shown
val isHigherPriorityMessage = (message?.priority ?: 0) > (cache.lastShownMessage?.priority ?: 0) val isHigherPriorityMessage = (message?.priority ?: 0) > (cache.lastShownMessage?.priority ?: 0)
val result = when { val result =
when {
message == null -> null message == null -> null
message is RuntimeMessage -> message message is RuntimeMessage -> message
isSameMessageUpdate -> message isSameMessageUpdate -> message
isHigherPriorityMessage -> if (hasNoMessageBeenShownLately) { isHigherPriorityMessage ->
if (hasNoMessageBeenShownLately) {
if (someMessageBeenShown) null else message if (someMessageBeenShown) null else message
} else { } else {
message message

View File

@ -29,8 +29,19 @@ class NavigateToErrorUseCase(
} }
sealed interface ErrorArgs { sealed interface ErrorArgs {
data class SyncError(val synchronizerError: SynchronizerError) : ErrorArgs data class SyncError(
data class ShieldingError(val error: SubmitResult.Failure): ErrorArgs val synchronizerError: SynchronizerError
data class ShieldingGeneralError(val exception: Exception): ErrorArgs ) : ErrorArgs
data class General(val exception: Exception): ErrorArgs
data class ShieldingError(
val error: SubmitResult.Failure
) : ErrorArgs
data class ShieldingGeneralError(
val exception: Exception
) : ErrorArgs
data class General(
val exception: Exception
) : ErrorArgs
} }

View File

@ -1,7 +1,6 @@
package co.electriccoin.zcash.ui.common.usecase 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.repository.ShieldFundsRepository import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
class RemindShieldFundsLaterUseCase( class RemindShieldFundsLaterUseCase(

View File

@ -50,7 +50,6 @@ class SendEmailUseCase(
runCatching { context.startActivity(mailIntent) } runCatching { context.startActivity(mailIntent) }
} }
suspend operator fun invoke(synchronizerError: SynchronizerError) { suspend operator fun invoke(synchronizerError: SynchronizerError) {
val fullMessage = val fullMessage =
EmailUtil.formatMessage( EmailUtil.formatMessage(

View File

@ -46,11 +46,13 @@ class ShieldFundsUseCase(
} }
} }
@Suppress("TooGenericExceptionCaught")
private suspend fun shieldZashiFunds() { private suspend fun shieldZashiFunds() {
try { try {
zashiProposalRepository.createShieldProposal() zashiProposalRepository.createShieldProposal()
zashiProposalRepository.submitTransaction() zashiProposalRepository.submitTransaction()
val result = zashiProposalRepository.submitState val result =
zashiProposalRepository.submitState
.filterIsInstance<SubmitProposalState.Result>() .filterIsInstance<SubmitProposalState.Result>()
.first() .first()
.submitResult .submitResult
@ -65,6 +67,7 @@ class ShieldFundsUseCase(
} }
} }
@Suppress("TooGenericExceptionCaught")
private suspend fun createKeystoneShieldProposal() { private suspend fun createKeystoneShieldProposal() {
try { try {
keystoneProposalRepository.createShieldProposal() keystoneProposalRepository.createShieldProposal()

View File

@ -8,8 +8,8 @@ import co.electriccoin.zcash.ui.NavigationTargets
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase
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.design.component.ButtonState import co.electriccoin.zcash.ui.design.component.ButtonState
import co.electriccoin.zcash.ui.design.component.listitem.ZashiListItemState import co.electriccoin.zcash.ui.design.component.listitem.ZashiListItemState
import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.design.util.stringRes

View File

@ -103,7 +103,8 @@ private fun BalanceWidgetPreview() {
state = state =
BalanceWidgetState( BalanceWidgetState(
totalBalance = Zatoshi(1234567891234567L), totalBalance = Zatoshi(1234567891234567L),
button = BalanceButtonState( button =
BalanceButtonState(
icon = R.drawable.ic_help, icon = R.drawable.ic_help,
text = stringRes("text"), text = stringRes("text"),
amount = Zatoshi(1000), amount = Zatoshi(1000),

View File

@ -41,7 +41,8 @@ internal fun BalanceWidgetButton(
state: BalanceButtonState, state: BalanceButtonState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val colors = ZashiButtonDefaults.secondaryColors( val colors =
ZashiButtonDefaults.secondaryColors(
containerColor = ZashiColors.Surfaces.bgPrimary, containerColor = ZashiColors.Surfaces.bgPrimary,
borderColor = ZashiColors.Utility.Gray.utilityGray100 borderColor = ZashiColors.Utility.Gray.utilityGray100
) )
@ -53,7 +54,8 @@ internal fun BalanceWidgetButton(
shape = RoundedCornerShape(ZashiDimensions.Radius.radiusIg), shape = RoundedCornerShape(ZashiDimensions.Radius.radiusIg),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 10.dp), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 10.dp),
colors = colors.toButtonColors(), colors = colors.toButtonColors(),
elevation = ButtonDefaults.buttonElevation( elevation =
ButtonDefaults.buttonElevation(
defaultElevation = 1.dp, defaultElevation = 1.dp,
pressedElevation = 0.dp pressedElevation = 0.dp
), ),
@ -110,10 +112,12 @@ data class BalanceButtonState(
@PreviewScreens @PreviewScreens
@Composable @Composable
private fun Preview() = ZcashTheme { private fun Preview() =
ZcashTheme {
BlankSurface { BlankSurface {
BalanceWidgetButton( BalanceWidgetButton(
state = BalanceButtonState( state =
BalanceButtonState(
icon = R.drawable.ic_help, icon = R.drawable.ic_help,
text = stringRes("text"), text = stringRes("text"),
amount = Zatoshi(1000), amount = Zatoshi(1000),

View File

@ -34,17 +34,20 @@ class BalanceWidgetViewModel(
}.stateIn( }.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = createState( initialValue =
createState(
account = accountDataSource.allAccounts.value?.firstOrNull { it.isSelected }, account = accountDataSource.allAccounts.value?.firstOrNull { it.isSelected },
exchangeRateUsd = exchangeRateRepository.state.value exchangeRateUsd = exchangeRateRepository.state.value
) )
) )
@Suppress("CyclomaticComplexMethod")
private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState) = private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState) =
BalanceWidgetState( BalanceWidgetState(
totalBalance = account?.totalBalance ?: Zatoshi(0), totalBalance = account?.totalBalance ?: Zatoshi(0),
exchangeRate = if (args.isExchangeRateButtonEnabled) exchangeRateUsd else null, exchangeRate = if (args.isExchangeRateButtonEnabled) exchangeRateUsd else null,
button = when { button =
when {
!args.isBalanceButtonEnabled -> null !args.isBalanceButtonEnabled -> null
account == null -> null account == null -> null
account.totalBalance == account.spendableShieldedBalance -> null account.totalBalance == account.spendableShieldedBalance -> null
@ -72,8 +75,8 @@ class BalanceWidgetViewModel(
onClick = ::onBalanceButtonClick onClick = ::onBalanceButtonClick
) )
account.totalBalance > account.spendableShieldedBalance ->
account.totalBalance > account.spendableShieldedBalance -> BalanceButtonState( BalanceButtonState(
icon = R.drawable.ic_balances_expand, icon = R.drawable.ic_balances_expand,
text = stringRes(R.string.widget_balances_button_spendable), text = stringRes(R.string.widget_balances_button_spendable),
amount = account.spendableShieldedBalance, amount = account.spendableShieldedBalance,

View File

@ -108,7 +108,8 @@ private fun BalanceActionRow(state: BalanceActionRowState) {
) )
Spacer(1f) Spacer(1f)
when (state.icon) { when (state.icon) {
is ImageResource.ByDrawable -> Image( is ImageResource.ByDrawable ->
Image(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
painter = painterResource(state.icon.resource), painter = painterResource(state.icon.resource),
contentDescription = null contentDescription = null
@ -122,7 +123,8 @@ private fun BalanceActionRow(state: BalanceActionRowState) {
SelectionContainer { SelectionContainer {
Text( Text(
text = state.value.getValue(), text = state.value.getValue(),
color = if (state.icon is ImageResource.Loading) { color =
if (state.icon is ImageResource.Loading) {
ZashiColors.Text.textTertiary ZashiColors.Text.textTertiary
} else { } else {
ZashiColors.Text.textPrimary ZashiColors.Text.textPrimary
@ -176,9 +178,11 @@ private fun BalanceShieldButton(state: BalanceShieldButtonState) {
} }
Spacer(1f) Spacer(1f)
ZashiButton( ZashiButton(
state = ButtonState( state =
ButtonState(
text = stringRes(R.string.balance_action_shield), text = stringRes(R.string.balance_action_shield),
onClick = state.onShieldClick) onClick = state.onShieldClick
)
) )
} }
} }
@ -187,16 +191,20 @@ private fun BalanceShieldButton(state: BalanceShieldButtonState) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewScreens @PreviewScreens
@Composable @Composable
private fun Preview() = ZcashTheme { private fun Preview() =
ZcashTheme {
BalanceActionView( BalanceActionView(
state = BalanceActionState( state =
BalanceActionState(
title = stringRes("Title"), title = stringRes("Title"),
message = stringRes("Subtitle"), message = stringRes("Subtitle"),
positive = ButtonState( positive =
ButtonState(
text = stringRes("Positive") text = stringRes("Positive")
), ),
onBack = {}, onBack = {},
rows = listOf( rows =
listOf(
BalanceActionRowState( BalanceActionRowState(
title = stringRes("Row"), title = stringRes("Row"),
icon = loadingImageRes(), icon = loadingImageRes(),
@ -208,7 +216,8 @@ private fun Preview() = ZcashTheme {
value = stringRes("Value") value = stringRes("Value")
) )
), ),
shieldButton = BalanceShieldButtonState( shieldButton =
BalanceShieldButtonState(
amount = Zatoshi(10000), amount = Zatoshi(10000),
onShieldClick = {} onShieldClick = {}
) )

View File

@ -23,14 +23,19 @@ class BalanceActionViewModel(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val shieldFunds: ShieldFundsUseCase, private val shieldFunds: ShieldFundsUseCase,
) : ViewModel() { ) : ViewModel() {
val state = accountDataSource.selectedAccount val state =
accountDataSource.selectedAccount
.mapNotNull { .mapNotNull {
createState(it) createState(it)
} }.stateIn(
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = createState(accountDataSource.allAccounts.value.orEmpty().firstOrNull { it.isSelected }) initialValue =
createState(
accountDataSource.allAccounts.value
.orEmpty()
.firstOrNull { it.isSelected }
)
) )
private fun createState(account: WalletAccount?): BalanceActionState? { private fun createState(account: WalletAccount?): BalanceActionState? {
@ -47,7 +52,8 @@ class BalanceActionViewModel(
} }
private fun createMessage(account: WalletAccount): StringResource { private fun createMessage(account: WalletAccount): StringResource {
val pending = when { val pending =
when {
account.totalShieldedBalance == account.spendableShieldedBalance -> account.totalShieldedBalance == account.spendableShieldedBalance ->
stringRes(R.string.balance_action_all_shielded) stringRes(R.string.balance_action_all_shielded)
@ -66,8 +72,10 @@ class BalanceActionViewModel(
} }
} }
private fun createPositiveButton(account: WalletAccount) = ButtonState( private fun createPositiveButton(account: WalletAccount) =
text = if (account.isShieldingAvailable) { ButtonState(
text =
if (account.isShieldingAvailable) {
stringRes(R.string.general_dismiss) stringRes(R.string.general_dismiss)
} else { } else {
stringRes(R.string.general_ok) stringRes(R.string.general_ok)
@ -75,7 +83,8 @@ class BalanceActionViewModel(
onClick = ::onBack onClick = ::onBack
) )
private fun createInfoRows(account: WalletAccount) = listOfNotNull( private fun createInfoRows(account: WalletAccount) =
listOfNotNull(
BalanceActionRowState( BalanceActionRowState(
title = stringRes(R.string.balance_action_info_shielded), title = stringRes(R.string.balance_action_info_shielded),
icon = imageRes(R.drawable.ic_balance_shield), icon = imageRes(R.drawable.ic_balance_shield),
@ -93,7 +102,8 @@ class BalanceActionViewModel(
BalanceActionRowState( BalanceActionRowState(
title = stringRes(R.string.balance_action_info_pending), title = stringRes(R.string.balance_action_info_pending),
icon = loadingImageRes(), icon = loadingImageRes(),
value = stringRes( value =
stringRes(
R.string.general_zec, R.string.general_zec,
stringRes(account.totalShieldedBalance - account.spendableShieldedBalance) stringRes(account.totalShieldedBalance - account.spendableShieldedBalance)
) )
@ -103,12 +113,11 @@ class BalanceActionViewModel(
}, },
) )
private fun createShieldButtonState(account: WalletAccount): BalanceShieldButtonState? { private fun createShieldButtonState(account: WalletAccount): BalanceShieldButtonState? =
return BalanceShieldButtonState( BalanceShieldButtonState(
amount = account.transparent.balance, amount = account.transparent.balance,
onShieldClick = ::onShieldClick onShieldClick = ::onShieldClick
).takeIf { account.isShieldingAvailable } ).takeIf { account.isShieldingAvailable }
}
private fun onBack() = navigationRouter.back() private fun onBack() = navigationRouter.back()

View File

@ -7,7 +7,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.MainActivity import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R

View File

@ -85,15 +85,19 @@ fun BottomSheetContent(state: ErrorState, modifier: Modifier = Modifier) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@PreviewScreens @PreviewScreens
@Composable @Composable
private fun Preview() = ZcashTheme { private fun Preview() =
ZcashTheme {
BottomSheetErrorView( BottomSheetErrorView(
state = ErrorState( state =
ErrorState(
title = stringRes("Error"), title = stringRes("Error"),
message = stringRes("Something went wrong"), message = stringRes("Something went wrong"),
positive = ButtonState( positive =
ButtonState(
text = stringRes("Positive") text = stringRes("Positive")
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes("Negative") text = stringRes("Negative")
), ),
onBack = {} onBack = {}

View File

@ -11,7 +11,8 @@ import co.electriccoin.zcash.ui.design.util.stringRes
@Composable @Composable
fun DialogView(state: ErrorState?) { fun DialogView(state: ErrorState?) {
ZashiScreenDialog( ZashiScreenDialog(
state = state?.let { state =
state?.let {
DialogState( DialogState(
title = it.title, title = it.title,
message = it.message, message = it.message,
@ -25,15 +26,19 @@ fun DialogView(state: ErrorState?) {
@PreviewScreens @PreviewScreens
@Composable @Composable
private fun Preview() = ZcashTheme { private fun Preview() =
ZcashTheme {
DialogView( DialogView(
state = ErrorState( state =
ErrorState(
title = stringRes("Error"), title = stringRes("Error"),
message = stringRes("Something went wrong"), message = stringRes("Something went wrong"),
positive = ButtonState( positive =
ButtonState(
text = stringRes("Positive") text = stringRes("Positive")
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes("Negative") text = stringRes("Negative")
), ),
onBack = {} onBack = {}

View File

@ -33,93 +33,112 @@ class ErrorViewModel(
private fun onBack() = navigationRouter.back() private fun onBack() = navigationRouter.back()
private fun createState(args: ErrorArgs): ErrorState = when (args) { private fun createState(args: ErrorArgs): ErrorState =
when (args) {
is ErrorArgs.SyncError -> createSyncErrorState(args) is ErrorArgs.SyncError -> createSyncErrorState(args)
is ErrorArgs.ShieldingError -> createShieldingErrorState(args) is ErrorArgs.ShieldingError -> createShieldingErrorState(args)
is ErrorArgs.General -> createGeneralErrorState(args) is ErrorArgs.General -> createGeneralErrorState(args)
is ErrorArgs.ShieldingGeneralError -> createGeneralShieldingErrorState(args) is ErrorArgs.ShieldingGeneralError -> createGeneralShieldingErrorState(args)
} }
private fun createSyncErrorState(args: ErrorArgs.SyncError) = ErrorState( private fun createSyncErrorState(args: ErrorArgs.SyncError) =
ErrorState(
title = stringRes(R.string.error_sync_title), title = stringRes(R.string.error_sync_title),
message = stringRes(args.synchronizerError.getStackTrace(STACKTRACE_LIMIT).orEmpty()), message = stringRes(args.synchronizerError.getStackTrace(STACKTRACE_LIMIT).orEmpty()),
positive = ButtonState( positive =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = { navigationRouter.back() } onClick = { navigationRouter.back() }
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes(R.string.general_report), text = stringRes(R.string.general_report),
onClick = { sendReportClick(args) } onClick = { sendReportClick(args) }
), ),
onBack = ::onBack, onBack = ::onBack,
) )
private fun createShieldingErrorState(args: ErrorArgs.ShieldingError) = ErrorState( private fun createShieldingErrorState(args: ErrorArgs.ShieldingError) =
ErrorState(
title = stringRes(R.string.error_shielding_title), title = stringRes(R.string.error_shielding_title),
message = when (args.error) { message =
when (args.error) {
is SubmitResult.MultipleTrxFailure -> stringRes(R.string.error_shielding_message_grpc) is SubmitResult.MultipleTrxFailure -> stringRes(R.string.error_shielding_message_grpc)
is SubmitResult.SimpleTrxFailure -> stringRes( is SubmitResult.SimpleTrxFailure ->
stringRes(
R.string.error_shielding_message, R.string.error_shielding_message,
stringRes(args.error.toErrorStacktrace()) stringRes(args.error.toErrorStacktrace())
) )
}, },
positive = ButtonState( positive =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = { navigationRouter.back() } onClick = { navigationRouter.back() }
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes(R.string.general_report), text = stringRes(R.string.general_report),
onClick = { sendReportClick(args) } onClick = { sendReportClick(args) }
), ),
onBack = ::onBack, onBack = ::onBack,
) )
private fun createGeneralErrorState(args: ErrorArgs.General) = ErrorState( private fun createGeneralErrorState(args: ErrorArgs.General) =
ErrorState(
title = stringRes(R.string.error_general_title), title = stringRes(R.string.error_general_title),
message = stringRes(R.string.error_general_message), message = stringRes(R.string.error_general_message),
positive = ButtonState( positive =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = { navigationRouter.back() } onClick = { navigationRouter.back() }
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes(R.string.general_report), text = stringRes(R.string.general_report),
onClick = { sendReportClick(args.exception) } onClick = { sendReportClick(args.exception) }
), ),
onBack = ::onBack, onBack = ::onBack,
) )
private fun createGeneralShieldingErrorState(args: ErrorArgs.ShieldingGeneralError) = ErrorState( private fun createGeneralShieldingErrorState(args: ErrorArgs.ShieldingGeneralError) =
ErrorState(
title = stringRes(R.string.error_shielding_title), title = stringRes(R.string.error_shielding_title),
message = stringRes( message =
stringRes(
R.string.error_shielding_message, R.string.error_shielding_message,
stringRes(args.exception.stackTraceToString().take(STACKTRACE_LIMIT)) stringRes(args.exception.stackTraceToString().take(STACKTRACE_LIMIT))
), ),
positive = ButtonState( positive =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = { navigationRouter.back() } onClick = { navigationRouter.back() }
), ),
negative = ButtonState( negative =
ButtonState(
text = stringRes(R.string.general_report), text = stringRes(R.string.general_report),
onClick = { sendReportClick(args.exception) } onClick = { sendReportClick(args.exception) }
), ),
onBack = ::onBack, onBack = ::onBack,
) )
private fun sendReportClick(args: ErrorArgs.ShieldingError) = viewModelScope.launch { private fun sendReportClick(args: ErrorArgs.ShieldingError) =
viewModelScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
navigationRouter.back() navigationRouter.back()
sendEmailUseCase(args.error) sendEmailUseCase(args.error)
} }
} }
private fun sendReportClick(args: ErrorArgs.SyncError) = viewModelScope.launch { private fun sendReportClick(args: ErrorArgs.SyncError) =
viewModelScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
navigationRouter.back() navigationRouter.back()
sendEmailUseCase(args.synchronizerError) sendEmailUseCase(args.synchronizerError)
} }
} }
private fun sendReportClick(exception: Exception) = viewModelScope.launch { private fun sendReportClick(exception: Exception) =
viewModelScope.launch {
withContext(NonCancellable) { withContext(NonCancellable) {
navigationRouter.back() navigationRouter.back()
sendEmailUseCase(exception) sendEmailUseCase(exception)

View File

@ -77,7 +77,8 @@ private fun CurrencyConversionOptInPreview() =
ZcashTheme { ZcashTheme {
BlankSurface { BlankSurface {
ExchangeRateOptInView( ExchangeRateOptInView(
state = ExchangeRateOptInState( state =
ExchangeRateOptInState(
onEnableClick = {}, onEnableClick = {},
onBack = {}, onBack = {},
onSkipClick = {}, onSkipClick = {},

View File

@ -10,7 +10,8 @@ class ExchangeRateOptInViewModel(
private val exchangeRateRepository: ExchangeRateRepository, private val exchangeRateRepository: ExchangeRateRepository,
private val navigationRouter: NavigationRouter private val navigationRouter: NavigationRouter
) : ViewModel() { ) : ViewModel() {
val state: StateFlow<ExchangeRateOptInState> = MutableStateFlow( val state: StateFlow<ExchangeRateOptInState> =
MutableStateFlow(
ExchangeRateOptInState( ExchangeRateOptInState(
onBack = ::dismissOptInExchangeRateUsd, onBack = ::dismissOptInExchangeRateUsd,
onEnableClick = ::optInExchangeRateUsd, onEnableClick = ::optInExchangeRateUsd,

View File

@ -187,7 +187,8 @@ private fun SettingsExchangeRateOptInPreview() =
ZcashTheme { ZcashTheme {
BlankSurface { BlankSurface {
ExchangeRateSettingsView( ExchangeRateSettingsView(
state = ExchangeRateSettingsState( state =
ExchangeRateSettingsState(
isOptedIn = true, isOptedIn = true,
onSaveClick = {}, onSaveClick = {},
onDismiss = {} onDismiss = {}

View File

@ -15,12 +15,11 @@ class ExchangeRateSettingsViewModel(
private val exchangeRateRepository: ExchangeRateRepository, private val exchangeRateRepository: ExchangeRateRepository,
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
) : ViewModel() { ) : ViewModel() {
val state =
val state = exchangeRateRepository.state exchangeRateRepository.state
.map { .map {
createState(it) createState(it)
} }.stateIn(
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = createState(exchangeRateRepository.state.value) initialValue = createState(exchangeRateRepository.state.value)

View File

@ -18,7 +18,8 @@ import org.koin.core.parameter.parametersOf
@Composable @Composable
internal fun AndroidHome() { internal fun AndroidHome() {
val topAppBarViewModel = koinActivityViewModel<ZashiTopAppBarViewModel>() val topAppBarViewModel = koinActivityViewModel<ZashiTopAppBarViewModel>()
val balanceWidgetViewModel = koinViewModel<BalanceWidgetViewModel> { val balanceWidgetViewModel =
koinViewModel<BalanceWidgetViewModel> {
parametersOf( parametersOf(
BalanceWidgetArgs( BalanceWidgetArgs(
isBalanceButtonEnabled = false, isBalanceButtonEnabled = false,

View File

@ -47,10 +47,10 @@ import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportMessage
import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportMessageState import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportMessageState
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessage 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.WalletSyncingMessageState
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessage 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.shieldfunds.ShieldFundsMessageState
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.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
@ -243,6 +243,7 @@ private fun <T> animationSpec(delay: Duration? = null): TweenSpec<T> {
) )
} }
@Suppress("MagicNumber")
private fun <T> elevationAnimationSpec(delay: Duration? = null): TweenSpec<T> { private fun <T> elevationAnimationSpec(delay: Duration? = null): TweenSpec<T> {
val delayMs = delay?.inWholeMilliseconds?.toInt() ?: 0 val delayMs = delay?.inWholeMilliseconds?.toInt() ?: 0
return tween( return tween(

View File

@ -107,8 +107,7 @@ private fun Container(
0f to ZashiLightColors.Utility.Purple.utilityPurple500, 0f to ZashiLightColors.Utility.Purple.utilityPurple500,
1f to ZashiLightColors.Utility.Purple.utilityPurple900, 1f to ZashiLightColors.Utility.Purple.utilityPurple900,
) )
) ).clickable(onClick = onClick)
.clickable(onClick = onClick)
.padding(contentPadding), .padding(contentPadding),
) { ) {
Row( Row(

View File

@ -32,8 +32,8 @@ import co.electriccoin.zcash.ui.design.util.scaffoldPadding
import co.electriccoin.zcash.ui.design.util.stringRes import co.electriccoin.zcash.ui.design.util.stringRes
import co.electriccoin.zcash.ui.fixture.BalanceStateFixture import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
import co.electriccoin.zcash.ui.fixture.ZashiMainTopAppBarStateFixture import co.electriccoin.zcash.ui.fixture.ZashiMainTopAppBarStateFixture
import co.electriccoin.zcash.ui.screen.balances.BalanceWidgetState
import co.electriccoin.zcash.ui.screen.balances.BalanceWidget import co.electriccoin.zcash.ui.screen.balances.BalanceWidget
import co.electriccoin.zcash.ui.screen.balances.BalanceWidgetState
import co.electriccoin.zcash.ui.screen.home.error.WalletErrorMessageState import co.electriccoin.zcash.ui.screen.home.error.WalletErrorMessageState
import co.electriccoin.zcash.ui.screen.transactionhistory.widget.TransactionHistoryWidgetState import co.electriccoin.zcash.ui.screen.transactionhistory.widget.TransactionHistoryWidgetState
import co.electriccoin.zcash.ui.screen.transactionhistory.widget.TransactionHistoryWidgetStateFixture import co.electriccoin.zcash.ui.screen.transactionhistory.widget.TransactionHistoryWidgetStateFixture

View File

@ -28,8 +28,8 @@ import co.electriccoin.zcash.ui.screen.home.currency.EnableCurrencyConversionMes
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.reporting.CrashReportOptIn
import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportMessageState import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportMessageState
import co.electriccoin.zcash.ui.screen.home.reporting.CrashReportOptIn
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.restoring.WalletRestoringMessageState
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo
@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Suppress("TooManyFunctions")
class HomeViewModel( class HomeViewModel(
getHomeMessage: GetHomeMessageUseCase, getHomeMessage: GetHomeMessageUseCase,
getVersionInfoProvider: GetVersionInfoProvider, getVersionInfoProvider: GetVersionInfoProvider,
@ -63,8 +64,8 @@ class HomeViewModel(
private val shieldFunds: ShieldFundsUseCase, private val shieldFunds: ShieldFundsUseCase,
private val navigateToError: NavigateToErrorUseCase private val navigateToError: NavigateToErrorUseCase
) : ViewModel() { ) : ViewModel() {
private val messageState =
private val messageState = getHomeMessage getHomeMessage
.observe() .observe()
.map { createMessageState(it) } .map { createMessageState(it) }
.stateIn( .stateIn(
@ -155,37 +156,46 @@ class HomeViewModel(
message = messageState message = messageState
) )
private fun createMessageState(it: HomeMessageData?) = when (it) { private fun createMessageState(it: HomeMessageData?) =
is HomeMessageData.Backup -> WalletBackupMessageState( when (it) {
is HomeMessageData.Backup ->
WalletBackupMessageState(
onClick = ::onWalletBackupMessageClick, onClick = ::onWalletBackupMessageClick,
onButtonClick = ::onWalletBackupMessageButtonClick, onButtonClick = ::onWalletBackupMessageButtonClick,
) )
HomeMessageData.Disconnected -> WalletDisconnectedMessageState( HomeMessageData.Disconnected ->
WalletDisconnectedMessageState(
onClick = ::onWalletDisconnectedMessageClick onClick = ::onWalletDisconnectedMessageClick
) )
HomeMessageData.EnableCurrencyConversion -> EnableCurrencyConversionMessageState( HomeMessageData.EnableCurrencyConversion ->
EnableCurrencyConversionMessageState(
onClick = ::onEnableCurrencyConversionClick, onClick = ::onEnableCurrencyConversionClick,
onButtonClick = ::onEnableCurrencyConversionClick onButtonClick = ::onEnableCurrencyConversionClick
) )
is HomeMessageData.Error -> WalletErrorMessageState( is HomeMessageData.Error ->
WalletErrorMessageState(
onClick = { onWalletErrorMessageClick(it) } onClick = { onWalletErrorMessageClick(it) }
) )
is HomeMessageData.Restoring -> WalletRestoringMessageState( is HomeMessageData.Restoring ->
WalletRestoringMessageState(
progress = it.progress, progress = it.progress,
onClick = ::onWalletRestoringMessageClick onClick = ::onWalletRestoringMessageClick
) )
is HomeMessageData.Syncing -> WalletSyncingMessageState( is HomeMessageData.Syncing ->
WalletSyncingMessageState(
progress = it.progress, progress = it.progress,
onClick = ::onWalletSyncingMessageClick onClick = ::onWalletSyncingMessageClick
) )
is HomeMessageData.ShieldFunds -> ShieldFundsMessageState( is HomeMessageData.ShieldFunds ->
subtitle = stringRes( ShieldFundsMessageState(
subtitle =
stringRes(
R.string.home_message_transparent_balance_subtitle, R.string.home_message_transparent_balance_subtitle,
stringRes(it.zatoshi) stringRes(it.zatoshi)
), ),
@ -193,11 +203,13 @@ class HomeViewModel(
onButtonClick = ::onShieldFundsMessageButtonClick, onButtonClick = ::onShieldFundsMessageButtonClick,
) )
HomeMessageData.Updating -> WalletUpdatingMessageState( HomeMessageData.Updating ->
WalletUpdatingMessageState(
onClick = ::onWalletUpdatingMessageClick onClick = ::onWalletUpdatingMessageClick
) )
HomeMessageData.CrashReport -> CrashReportMessageState( HomeMessageData.CrashReport ->
CrashReportMessageState(
onClick = ::onCrashReportMessageClick, onClick = ::onCrashReportMessageClick,
onButtonClick = ::onCrashReportMessageClick onButtonClick = ::onCrashReportMessageClick
) )

View File

@ -20,10 +20,8 @@ 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.HorizontalSpacer
import co.electriccoin.zcash.ui.design.component.IconButtonState import co.electriccoin.zcash.ui.design.component.IconButtonState
import co.electriccoin.zcash.ui.design.component.Spacer import co.electriccoin.zcash.ui.design.component.Spacer
import co.electriccoin.zcash.ui.design.component.VerticalSpacer
import co.electriccoin.zcash.ui.design.component.ZashiButton import co.electriccoin.zcash.ui.design.component.ZashiButton
import co.electriccoin.zcash.ui.design.component.ZashiIconButton import co.electriccoin.zcash.ui.design.component.ZashiIconButton
import co.electriccoin.zcash.ui.design.component.ZashiInfoRow import co.electriccoin.zcash.ui.design.component.ZashiInfoRow

View File

@ -14,7 +14,8 @@ class WalletBackupDetailViewModel(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val navigateToWalletBackup: NavigateToWalletBackupUseCase private val navigateToWalletBackup: NavigateToWalletBackupUseCase
) : ViewModel() { ) : ViewModel() {
val state = MutableStateFlow( val state =
MutableStateFlow(
WalletBackupDetailState( WalletBackupDetailState(
onBack = ::onBack, onBack = ::onBack,
onNextClick = ::onNextClick, onNextClick = ::onNextClick,
@ -39,5 +40,3 @@ class WalletBackupDetailViewModel(
} }
} }
} }

View File

@ -42,11 +42,11 @@ fun WalletBackupInfoView(
state = state state = state
) { ) {
Column( Column(
modifier = Modifier modifier =
Modifier
.weight(1f, false) .weight(1f, false)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp) .padding(horizontal = 24.dp)
) { ) {
Image( Image(
painter = painterResource(R.drawable.ic_info_backup), painter = painterResource(R.drawable.ic_info_backup),
@ -118,16 +118,19 @@ private fun Preview() =
WalletBackupInfoView( WalletBackupInfoView(
WalletBackupInfoState( WalletBackupInfoState(
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 isEnabled = false
), ),
primaryButton = ButtonState( primaryButton =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = {} onClick = {}
), ),
checkboxState = CheckboxState( checkboxState =
CheckboxState(
isChecked = false, isChecked = false,
onClick = {}, onClick = {},
text = stringRes(R.string.home_info_backup_checkbox) text = stringRes(R.string.home_info_backup_checkbox)

View File

@ -31,10 +31,10 @@ class WalletBackupInfoViewModel(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase, private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : ViewModel() { ) : ViewModel() {
private val isConsentChecked = MutableStateFlow(false) private val isConsentChecked = MutableStateFlow(false)
private val lockoutDuration = walletBackupDataSource private val lockoutDuration =
walletBackupDataSource
.observe() .observe()
.filterIsInstance<WalletBackupAvailability.Available>() .filterIsInstance<WalletBackupAvailability.Available>()
.take(1) .take(1)
@ -45,30 +45,33 @@ class WalletBackupInfoViewModel(
initialValue = null initialValue = null
) )
val state: StateFlow<WalletBackupInfoState?> = combine( val state: StateFlow<WalletBackupInfoState?> =
combine(
lockoutDuration.filterNotNull(), lockoutDuration.filterNotNull(),
isConsentChecked, isConsentChecked,
walletBackupConsentStorageProvider.observe().take(1) walletBackupConsentStorageProvider.observe().take(1)
) { lockout, isConsentChecked, isConsentSaved -> ) { lockout, isConsentChecked, isConsentSaved ->
WalletBackupInfoState( WalletBackupInfoState(
onBack = ::onBack, onBack = ::onBack,
secondaryButton = ButtonState( secondaryButton =
ButtonState(
text = stringRes(R.string.general_remind_me_in, stringRes(lockout.res)), text = stringRes(R.string.general_remind_me_in, stringRes(lockout.res)),
onClick = ::onRemindMeLaterClick, onClick = ::onRemindMeLaterClick,
isEnabled = isConsentChecked || isConsentSaved isEnabled = isConsentChecked || isConsentSaved
), ),
checkboxState = CheckboxState( checkboxState =
CheckboxState(
isChecked = isConsentChecked, isChecked = isConsentChecked,
onClick = ::onConsentClick, onClick = ::onConsentClick,
text = stringRes(R.string.home_info_backup_checkbox) text = stringRes(R.string.home_info_backup_checkbox)
).takeIf { !isConsentSaved }, ).takeIf { !isConsentSaved },
primaryButton = ButtonState( primaryButton =
ButtonState(
text = stringRes(R.string.general_ok), text = stringRes(R.string.general_ok),
onClick = ::onPrimaryClick onClick = ::onPrimaryClick
) )
) )
} }.stateIn(
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null initialValue = null

View File

@ -62,7 +62,10 @@ fun CrashReportMessage(
) )
} }
class CrashReportMessageState(val onClick: () -> Unit, val onButtonClick: () -> Unit) : HomeMessageState class CrashReportMessageState(
val onClick: () -> Unit,
val onButtonClick: () -> Unit
) : HomeMessageState
@PreviewScreens @PreviewScreens
@Composable @Composable

View File

@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.BlankSurface import co.electriccoin.zcash.ui.design.component.BlankSurface
import co.electriccoin.zcash.ui.design.component.ZashiBaseSettingsOptIn
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.ZashiInfoRow import co.electriccoin.zcash.ui.design.component.ZashiInfoRow
@ -20,7 +21,6 @@ import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
import co.electriccoin.zcash.ui.design.component.ZashiBaseSettingsOptIn
@Composable @Composable
fun CrashReportOptInView(state: CrashReportOptInState) { fun CrashReportOptInView(state: CrashReportOptInState) {
@ -76,7 +76,8 @@ private fun Preview() =
ZcashTheme { ZcashTheme {
BlankSurface { BlankSurface {
CrashReportOptInView( CrashReportOptInView(
state = CrashReportOptInState( state =
CrashReportOptInState(
onOptInClick = {}, onOptInClick = {},
onBack = {}, onBack = {},
onOptOutClick = {}, onOptOutClick = {},

View File

@ -10,7 +10,8 @@ class CrashReportOptInViewModel(
private val exchangeRateRepository: ExchangeRateRepository, private val exchangeRateRepository: ExchangeRateRepository,
private val navigationRouter: NavigationRouter private val navigationRouter: NavigationRouter
) : ViewModel() { ) : ViewModel() {
val state: StateFlow<CrashReportOptInState> = MutableStateFlow( val state: StateFlow<CrashReportOptInState> =
MutableStateFlow(
CrashReportOptInState( CrashReportOptInState(
onBack = ::dismissOptInExchangeRateUsd, onBack = ::dismissOptInExchangeRateUsd,
onOptInClick = ::optInExchangeRateUsd, onOptInClick = ::optInExchangeRateUsd,

View File

@ -6,7 +6,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi
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.repository.ShieldFundsData import co.electriccoin.zcash.ui.common.repository.ShieldFundsData
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
@ -32,8 +31,8 @@ class ShieldFundsInfoViewModel(
private val remindShieldFundsLater: RemindShieldFundsLaterUseCase, private val remindShieldFundsLater: RemindShieldFundsLaterUseCase,
private val shieldFunds: ShieldFundsUseCase, private val shieldFunds: ShieldFundsUseCase,
) : ViewModel() { ) : ViewModel() {
private val lockoutDuration =
private val lockoutDuration = shieldFundsRepository shieldFundsRepository
.availability .availability
.filterIsInstance<ShieldFundsData.Available>() .filterIsInstance<ShieldFundsData.Available>()
.take(1) .take(1)
@ -63,8 +62,7 @@ class ShieldFundsInfoViewModel(
), ),
transparentAmount = account?.transparent?.balance ?: Zatoshi(0) transparentAmount = account?.transparent?.balance ?: Zatoshi(0)
) )
} }.stateIn(
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null initialValue = null
@ -76,4 +74,3 @@ class ShieldFundsInfoViewModel(
private fun onShieldClick() = shieldFunds(closeCurrentScreen = true) private fun onShieldClick() = shieldFunds(closeCurrentScreen = true)
} }

View File

@ -58,7 +58,6 @@ fun MainActivity.OnboardingNavigation() {
val flexaViewModel = koinViewModel<FlexaViewModel>() val flexaViewModel = koinViewModel<FlexaViewModel>()
val messageAvailabilityDataSource = koinInject<MessageAvailabilityDataSource>() val messageAvailabilityDataSource = koinInject<MessageAvailabilityDataSource>()
val navigator: Navigator = val navigator: Navigator =
remember( remember(
navController, navController,

View File

@ -17,4 +17,6 @@ fun AndroidRestoreBDDate(args: RestoreBDDate) {
} }
@Serializable @Serializable
data class RestoreBDDate(val seed: String) data class RestoreBDDate(
val seed: String
)

View File

@ -31,14 +31,14 @@ class RestoreBDDateViewModel(
private val navigationRouter: NavigationRouter, private val navigationRouter: NavigationRouter,
private val context: Context, private val context: Context,
) : ViewModel() { ) : ViewModel() {
@Suppress("MagicNumber")
private val selection = MutableStateFlow<YearMonth>(YearMonth.of(2018, 10)) private val selection = MutableStateFlow<YearMonth>(YearMonth.of(2018, 10))
val state: StateFlow<RestoreBDDateState?> = selection val state: StateFlow<RestoreBDDateState?> =
selection
.map { .map {
createState(it) createState(it)
} }.stateIn(
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
initialValue = null initialValue = null
@ -59,12 +59,15 @@ class RestoreBDDateViewModel(
private fun onEstimateClick() { private fun onEstimateClick() {
viewModelScope.launch { viewModelScope.launch {
val instant = selection.value.atDay(1) val instant =
selection.value
.atDay(1)
.atStartOfDay() .atStartOfDay()
.atZone(ZoneId.systemDefault()) .atZone(ZoneId.systemDefault())
.toInstant() .toInstant()
.toKotlinInstant() .toKotlinInstant()
val bday = SdkSynchronizer.estimateBirthdayHeight( val bday =
SdkSynchronizer.estimateBirthdayHeight(
context = context, context = context,
date = instant, date = instant,
network = ZcashNetwork.fromResources(context) network = ZcashNetwork.fromResources(context)

View File

@ -75,8 +75,8 @@ import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
import co.electriccoin.zcash.ui.design.util.scaffoldPadding import co.electriccoin.zcash.ui.design.util.scaffoldPadding
import co.electriccoin.zcash.ui.fixture.BalanceStateFixture import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
import co.electriccoin.zcash.ui.fixture.ZashiMainTopAppBarStateFixture import co.electriccoin.zcash.ui.fixture.ZashiMainTopAppBarStateFixture
import co.electriccoin.zcash.ui.screen.balances.BalanceWidgetState
import co.electriccoin.zcash.ui.screen.balances.BalanceWidget import co.electriccoin.zcash.ui.screen.balances.BalanceWidget
import co.electriccoin.zcash.ui.screen.balances.BalanceWidgetState
import co.electriccoin.zcash.ui.screen.send.SendTag import co.electriccoin.zcash.ui.screen.send.SendTag
import co.electriccoin.zcash.ui.screen.send.model.AmountState import co.electriccoin.zcash.ui.screen.send.model.AmountState
import co.electriccoin.zcash.ui.screen.send.model.MemoState import co.electriccoin.zcash.ui.screen.send.model.MemoState
@ -653,7 +653,8 @@ fun SendFormAmountTextField(
) )
}, },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
innerModifier = ZashiTextFieldDefaults innerModifier =
ZashiTextFieldDefaults
.innerModifier .innerModifier
.testTag(SendTag.SEND_AMOUNT_FIELD), .testTag(SendTag.SEND_AMOUNT_FIELD),
error = amountError, error = amountError,

View File

@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarViewModel import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarViewModel
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf

View File

@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.di.koinActivityViewModel import co.electriccoin.zcash.di.koinActivityViewModel
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarViewModel import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarViewModel
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@Composable @Composable

View File

@ -17,4 +17,6 @@ internal fun AndroidWalletBackup(args: WalletBackup) {
} }
@Serializable @Serializable
data class WalletBackup(val isOpenedFromSeedBackupInfo: Boolean) data class WalletBackup(
val isOpenedFromSeedBackupInfo: Boolean
)

View File

@ -291,7 +291,8 @@ private fun RevealedPreview() =
isRevealed = true, isRevealed = true,
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")), tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
) {}, ) {},
secondaryButton = ButtonState( secondaryButton =
ButtonState(
text = stringRes("Text"), text = stringRes("Text"),
icon = R.drawable.ic_seed_show, icon = R.drawable.ic_seed_show,
onClick = {}, onClick = {},
@ -331,7 +332,8 @@ private fun HiddenPreview() =
isRevealed = false, isRevealed = false,
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")), tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
) {}, ) {},
secondaryButton = ButtonState( secondaryButton =
ButtonState(
text = stringRes("Text"), text = stringRes("Text"),
icon = R.drawable.ic_seed_show, icon = R.drawable.ic_seed_show,
onClick = {}, onClick = {},

View File

@ -34,8 +34,8 @@ class WalletBackupViewModel(
private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase, private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase,
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase, private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
) : ViewModel() { ) : ViewModel() {
private val lockoutDuration =
private val lockoutDuration = walletBackupDataSource walletBackupDataSource
.observe() .observe()
.filterIsInstance<WalletBackupAvailability.Available>() .filterIsInstance<WalletBackupAvailability.Available>()
.take(1) .take(1)
@ -48,7 +48,8 @@ class WalletBackupViewModel(
private val isRevealed = MutableStateFlow(false) private val isRevealed = MutableStateFlow(false)
private val isRemindMeLaterButtonVisible = isRevealed private val isRemindMeLaterButtonVisible =
isRevealed
.map { isRevealed -> .map { isRevealed ->
isRevealed && args.isOpenedFromSeedBackupInfo isRevealed && args.isOpenedFromSeedBackupInfo
} }
@ -63,8 +64,10 @@ class WalletBackupViewModel(
lockoutDuration lockoutDuration
) { isRevealed, isRemindMeLaterButtonVisible, wallet, lockoutDuration -> ) { isRevealed, isRemindMeLaterButtonVisible, wallet, lockoutDuration ->
WalletBackupState( WalletBackupState(
secondaryButton = ButtonState( secondaryButton =
text = if (lockoutDuration != null) { ButtonState(
text =
if (lockoutDuration != null) {
stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res)) stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
} else { } else {
stringRes(R.string.general_remind_me_later) stringRes(R.string.general_remind_me_later)
@ -80,7 +83,8 @@ class WalletBackupViewModel(
isRevealed -> stringRes(R.string.seed_recovery_hide_button) isRevealed -> stringRes(R.string.seed_recovery_hide_button)
else -> stringRes(R.string.seed_recovery_reveal_button) else -> stringRes(R.string.seed_recovery_reveal_button)
}, },
onClick = if (isRevealed && args.isOpenedFromSeedBackupInfo) { onClick =
if (isRevealed && args.isOpenedFromSeedBackupInfo) {
{ onWalletBackupSavedClick() } { onWalletBackupSavedClick() }
} else { } else {
{ onRevealClick() } { onRevealClick() }
@ -144,4 +148,3 @@ class WalletBackupViewModel(
private fun onBack() = navigationRouter.back() private fun onBack() = navigationRouter.back()
} }

View File

@ -48,8 +48,8 @@ import co.electriccoin.zcash.ui.screen.balances.BalanceTag
import co.electriccoin.zcash.ui.screen.home.HomeTags import co.electriccoin.zcash.ui.screen.home.HomeTags
import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeightTags import co.electriccoin.zcash.ui.screen.restore.height.RestoreBDHeightTags
import co.electriccoin.zcash.ui.screen.restore.seed.RestoreSeedTag import co.electriccoin.zcash.ui.screen.restore.seed.RestoreSeedTag
import co.electriccoin.zcash.ui.screen.walletbackup.WalletBackup
import co.electriccoin.zcash.ui.screen.send.SendTag import co.electriccoin.zcash.ui.screen.send.SendTag
import co.electriccoin.zcash.ui.screen.walletbackup.WalletBackup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext