Code cleanup
This commit is contained in:
parent
d488daeafd
commit
0db65c410a
|
@ -3,7 +3,9 @@ package co.electriccoin.zcash.preference.model.entry
|
|||
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
||||
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) =
|
||||
preferenceProvider.getLong(key)?.let { Instant.ofEpochMilli(it) }
|
||||
|
||||
|
@ -11,4 +13,4 @@ class TimestampPreferenceDefault(override val key: PreferenceKey): PreferenceDef
|
|||
preferenceProvider: PreferenceProvider,
|
||||
newValue: Instant?
|
||||
) = preferenceProvider.putLong(key, newValue?.toEpochMilli())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,22 +65,23 @@ fun ZashiBigIconButton(
|
|||
Modifier.background(darkBgGradient)
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
event.changes.forEach { change ->
|
||||
if (change.changedToDown()) {
|
||||
isPressed = true
|
||||
}
|
||||
if (change.changedToUp()) {
|
||||
isPressed = false
|
||||
modifier =
|
||||
modifier
|
||||
.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
event.changes.forEach { change ->
|
||||
if (change.changedToDown()) {
|
||||
isPressed = true
|
||||
}
|
||||
if (change.changedToUp()) {
|
||||
isPressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
onClick = state.onClick,
|
||||
color = ZashiColors.Surfaces.bgPrimary,
|
||||
shape = RoundedCornerShape(22.dp),
|
||||
|
|
|
@ -40,4 +40,4 @@ fun ZashiInfoRow(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ fun ZashiScreenDialog(
|
|||
|
||||
@Composable
|
||||
private fun Dialog(
|
||||
modifier: Modifier = Modifier,
|
||||
positive: ButtonState,
|
||||
negative: ButtonState,
|
||||
onDismissRequest: (() -> Unit),
|
||||
title: StringResource,
|
||||
message: StringResource,
|
||||
onDismissRequest: (() -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
properties: DialogProperties = DialogProperties()
|
||||
) {
|
||||
AlertDialog(
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
|
@ -77,26 +77,28 @@ fun ZashiYearMonthWheelDatePicker(
|
|||
}
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Row (
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.Center),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.padding(top = 1.dp)
|
||||
.background(ZashiColors.Surfaces.bgSecondary, RoundedCornerShape(6.dp))
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.padding(top = 1.dp)
|
||||
.background(ZashiColors.Surfaces.bgSecondary, RoundedCornerShape(6.dp))
|
||||
)
|
||||
Spacer(36.dp)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.padding(top = 1.dp)
|
||||
.background(ZashiColors.Surfaces.bgSecondary, RoundedCornerShape(6.dp))
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.height(34.dp)
|
||||
.padding(top = 1.dp)
|
||||
.background(ZashiColors.Surfaces.bgSecondary, RoundedCornerShape(6.dp))
|
||||
)
|
||||
}
|
||||
Row(
|
||||
|
@ -109,9 +111,10 @@ fun ZashiYearMonthWheelDatePicker(
|
|||
itemVerticalOffset = verticallyVisibleItems,
|
||||
isInfiniteScroll = false,
|
||||
onFocusItem = {
|
||||
state = state.copy(
|
||||
selectedDate = state.selectedDate.withMonth(state.months[it].value)
|
||||
)
|
||||
state =
|
||||
state.copy(
|
||||
selectedDate = state.selectedDate.withMonth(state.months[it].value)
|
||||
)
|
||||
},
|
||||
itemContent = {
|
||||
Text(
|
||||
|
@ -133,18 +136,20 @@ fun ZashiYearMonthWheelDatePicker(
|
|||
isInfiniteScroll = false,
|
||||
onFocusItem = {
|
||||
val year = state.years[it]
|
||||
val normalizedSelectedMonth = getSelectedMonthForYear(
|
||||
year = year,
|
||||
selectedMonth = state.selectedDate.month,
|
||||
startYearMonth = startInclusive,
|
||||
endYearMonth = endInclusive
|
||||
)
|
||||
val normalizedSelectedMonth =
|
||||
getSelectedMonthForYear(
|
||||
year = year,
|
||||
selectedMonth = state.selectedDate.month,
|
||||
startYearMonth = startInclusive,
|
||||
endYearMonth = endInclusive
|
||||
)
|
||||
val months = getMonthsForYear(year, startInclusive, endInclusive)
|
||||
val selectedDate = state.selectedDate.withYear(year.value).withMonth(normalizedSelectedMonth.value)
|
||||
state = state.copy(
|
||||
selectedDate = selectedDate,
|
||||
months = months
|
||||
)
|
||||
state =
|
||||
state.copy(
|
||||
selectedDate = selectedDate,
|
||||
months = months
|
||||
)
|
||||
},
|
||||
itemContent = {
|
||||
Text(
|
||||
|
@ -161,8 +166,8 @@ fun ZashiYearMonthWheelDatePicker(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth: YearMonth): List<Month> {
|
||||
return when (year.value) {
|
||||
private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth: YearMonth): List<Month> =
|
||||
when (year.value) {
|
||||
startYearMonth.year -> {
|
||||
(startYearMonth.month.value..Month.DECEMBER.value).map { index ->
|
||||
Month.entries.first { it.value == index }
|
||||
|
@ -192,32 +197,32 @@ private fun getMonthsForYear(year: Year, startYearMonth: YearMonth, endYearMonth
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedMonthForYear(
|
||||
year: Year,
|
||||
selectedMonth: Month,
|
||||
startYearMonth: YearMonth,
|
||||
endYearMonth: YearMonth
|
||||
): Month {
|
||||
return when (year.value) {
|
||||
): Month =
|
||||
when (year.value) {
|
||||
startYearMonth.year -> {
|
||||
val months = (startYearMonth.month.value..Month.DECEMBER.value).map { index ->
|
||||
Month.entries.first { it.value == index }
|
||||
}
|
||||
val months =
|
||||
(startYearMonth.month.value..Month.DECEMBER.value).map { index ->
|
||||
Month.entries.first { it.value == index }
|
||||
}
|
||||
if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth)
|
||||
}
|
||||
|
||||
endYearMonth.year -> {
|
||||
val months = (Month.JANUARY.value..endYearMonth.month.value).map { index ->
|
||||
Month.entries.first { it.value == index }
|
||||
}
|
||||
val months =
|
||||
(Month.JANUARY.value..endYearMonth.month.value).map { index ->
|
||||
Month.entries.first { it.value == index }
|
||||
}
|
||||
if (selectedMonth in months) selectedMonth else months.findClosest(selectedMonth)
|
||||
}
|
||||
|
||||
else -> selectedMonth
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Month>.findClosest(target: Month): Month {
|
||||
var closestNumber = this[0] // Initialize with the first element
|
||||
|
@ -400,11 +405,12 @@ private data class InternalState(
|
|||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() = ZcashTheme {
|
||||
BlankSurface {
|
||||
ZashiYearMonthWheelDatePicker(
|
||||
selection = YearMonth.now(),
|
||||
onSelectionChange = {}
|
||||
)
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
ZashiYearMonthWheelDatePicker(
|
||||
selection = YearMonth.now(),
|
||||
onSelectionChange = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,18 @@ import androidx.compose.runtime.Stable
|
|||
sealed interface ImageResource {
|
||||
@Immutable
|
||||
@JvmInline
|
||||
value class ByDrawable(@DrawableRes val resource: Int) : ImageResource
|
||||
value class ByDrawable(
|
||||
@DrawableRes val resource: Int
|
||||
) : ImageResource
|
||||
|
||||
@JvmInline
|
||||
@Immutable
|
||||
value class DisplayString(val value: String) : ImageResource
|
||||
value class DisplayString(
|
||||
val value: String
|
||||
) : ImageResource
|
||||
|
||||
@Immutable
|
||||
data object Loading: ImageResource
|
||||
data object Loading : ImageResource
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
|
|
@ -64,7 +64,9 @@ sealed interface StringResource {
|
|||
}
|
||||
|
||||
@Immutable
|
||||
private data class CompositeStringResource(val resources: List<StringResource>): StringResource
|
||||
private data class CompositeStringResource(
|
||||
val resources: List<StringResource>
|
||||
) : StringResource
|
||||
|
||||
@Stable
|
||||
fun stringRes(
|
||||
|
@ -116,14 +118,15 @@ fun StringResource.getValue(
|
|||
convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth,
|
||||
convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress,
|
||||
convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId
|
||||
): String = getString(
|
||||
context = LocalContext.current,
|
||||
convertZatoshi = convertZatoshi,
|
||||
convertDateTime = convertDateTime,
|
||||
convertYearMonth = convertYearMonth,
|
||||
convertAddress = convertAddress,
|
||||
convertTransactionId = convertTransactionId
|
||||
)
|
||||
): String =
|
||||
getString(
|
||||
context = LocalContext.current,
|
||||
convertZatoshi = convertZatoshi,
|
||||
convertDateTime = convertDateTime,
|
||||
convertYearMonth = convertYearMonth,
|
||||
convertAddress = convertAddress,
|
||||
convertTransactionId = convertTransactionId
|
||||
)
|
||||
|
||||
@Suppress("SpreadOperator")
|
||||
fun StringResource.getString(
|
||||
|
@ -133,25 +136,27 @@ fun StringResource.getString(
|
|||
convertYearMonth: (YearMonth) -> String = StringResourceDefaults::convertYearMonth,
|
||||
convertAddress: (StringResource.ByAddress) -> String = StringResourceDefaults::convertAddress,
|
||||
convertTransactionId: (StringResource.ByTransactionId) -> String = StringResourceDefaults::convertTransactionId
|
||||
): String = when (this) {
|
||||
is StringResource.ByResource -> context.getString(resource, *args.normalize(context).toTypedArray())
|
||||
is StringResource.ByString -> value
|
||||
is StringResource.ByZatoshi -> convertZatoshi(zatoshi)
|
||||
is StringResource.ByDateTime -> convertDateTime(this)
|
||||
is StringResource.ByYearMonth -> convertYearMonth(yearMonth)
|
||||
is StringResource.ByAddress -> convertAddress(this)
|
||||
is StringResource.ByTransactionId -> convertTransactionId(this)
|
||||
is CompositeStringResource -> this.resources.joinToString(separator = "") {
|
||||
it.getString(
|
||||
context = context,
|
||||
convertZatoshi = convertZatoshi,
|
||||
convertDateTime = convertDateTime,
|
||||
convertYearMonth = convertYearMonth,
|
||||
convertAddress = convertAddress,
|
||||
convertTransactionId = convertTransactionId,
|
||||
)
|
||||
): String =
|
||||
when (this) {
|
||||
is StringResource.ByResource -> context.getString(resource, *args.normalize(context).toTypedArray())
|
||||
is StringResource.ByString -> value
|
||||
is StringResource.ByZatoshi -> convertZatoshi(zatoshi)
|
||||
is StringResource.ByDateTime -> convertDateTime(this)
|
||||
is StringResource.ByYearMonth -> convertYearMonth(yearMonth)
|
||||
is StringResource.ByAddress -> convertAddress(this)
|
||||
is StringResource.ByTransactionId -> convertTransactionId(this)
|
||||
is CompositeStringResource ->
|
||||
this.resources.joinToString(separator = "") {
|
||||
it.getString(
|
||||
context = context,
|
||||
convertZatoshi = convertZatoshi,
|
||||
convertDateTime = convertDateTime,
|
||||
convertYearMonth = convertYearMonth,
|
||||
convertAddress = convertAddress,
|
||||
convertTransactionId = convertTransactionId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Any>.normalize(context: Context): List<Any> =
|
||||
this.map {
|
||||
|
|
|
@ -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.scankeystone.viewmodel.ScanKeystonePCZTViewModel
|
||||
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.viewmodel.SelectKeystoneAccountViewModel
|
||||
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.viewmodel.TransactionNoteViewModel
|
||||
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.whatsnew.viewmodel.WhatsNewViewModel
|
||||
import org.koin.core.module.dsl.viewModel
|
||||
|
|
|
@ -154,7 +154,7 @@ class NavigatorImpl(
|
|||
}
|
||||
}
|
||||
|
||||
if (command.routes.lastOrNull() in listOf(ExternalUrl, co.electriccoin.zcash.ui.screen.flexa.Flexa) ) {
|
||||
if (command.routes.lastOrNull() in listOf(ExternalUrl, co.electriccoin.zcash.ui.screen.flexa.Flexa)) {
|
||||
messageAvailabilityDataSource.onThirdPartyUiShown()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,25 +16,28 @@ import kotlinx.coroutines.flow.update
|
|||
interface MessageAvailabilityDataSource {
|
||||
val canShowMessage: Flow<Boolean>
|
||||
val canShowShieldMessage: Flow<Boolean>
|
||||
|
||||
fun onMessageShown()
|
||||
|
||||
fun onThirdPartyUiShown()
|
||||
|
||||
fun onShieldingInitiated()
|
||||
}
|
||||
|
||||
class MessageAvailabilityDataSourceImpl(
|
||||
applicationStateProvider: ApplicationStateProvider
|
||||
) : MessageAvailabilityDataSource {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
private val state = MutableStateFlow(
|
||||
MessageAvailabilityData(
|
||||
isAppInForeground = true,
|
||||
isThirdPartyUiShown = false,
|
||||
hasMessageBeenShown = false,
|
||||
canShowShieldMessage = true
|
||||
private val state =
|
||||
MutableStateFlow(
|
||||
MessageAvailabilityData(
|
||||
isAppInForeground = true,
|
||||
isThirdPartyUiShown = false,
|
||||
hasMessageBeenShown = false,
|
||||
canShowShieldMessage = true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
override val canShowMessage: Flow<Boolean> = state.map { it.canShowMessage }.distinctUntilChanged()
|
||||
override val canShowShieldMessage: Flow<Boolean> = state.map { it.canShowShieldMessage }.distinctUntilChanged()
|
||||
|
@ -58,8 +61,7 @@ class MessageAvailabilityDataSourceImpl(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun onMessageShown() {
|
||||
|
|
|
@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.flowOf
|
|||
import java.time.Instant
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
interface ShieldFundsDataSource {
|
||||
suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability>
|
||||
|
@ -26,34 +25,37 @@ interface ShieldFundsDataSource {
|
|||
class ShieldFundsDataSourceImpl(
|
||||
private val shieldFundsRemindMeCountStorageProvider: ShieldFundsRemindMeCountStorageProvider,
|
||||
private val shieldFundsRemindMeTimestampStorageProvider: ShieldFundsRemindMeTimestampStorageProvider
|
||||
): ShieldFundsDataSource {
|
||||
|
||||
) : ShieldFundsDataSource {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> = combine(
|
||||
shieldFundsRemindMeCountStorageProvider.observe(forAccount),
|
||||
shieldFundsRemindMeTimestampStorageProvider.observe(forAccount)
|
||||
) { count, timestamp ->
|
||||
count to timestamp
|
||||
}.flatMapLatest {(count, timestamp) ->
|
||||
when {
|
||||
timestamp == null -> flowOf(ShieldFundsAvailability.Available(ShieldFundsLockoutDuration.TWO_DAYS))
|
||||
count == 1 -> calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = ShieldFundsLockoutDuration.TWO_DAYS,
|
||||
nextLockoutDuration = ShieldFundsLockoutDuration.TWO_WEEKS
|
||||
)
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<ShieldFundsAvailability> =
|
||||
combine(
|
||||
shieldFundsRemindMeCountStorageProvider.observe(forAccount),
|
||||
shieldFundsRemindMeTimestampStorageProvider.observe(forAccount)
|
||||
) { count, timestamp ->
|
||||
count to timestamp
|
||||
}.flatMapLatest { (count, timestamp) ->
|
||||
when {
|
||||
timestamp == null -> flowOf(ShieldFundsAvailability.Available(ShieldFundsLockoutDuration.TWO_DAYS))
|
||||
count == 1 ->
|
||||
calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = ShieldFundsLockoutDuration.TWO_DAYS,
|
||||
nextLockoutDuration = ShieldFundsLockoutDuration.TWO_WEEKS
|
||||
)
|
||||
|
||||
else -> calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = if (count == 2) {
|
||||
ShieldFundsLockoutDuration.TWO_WEEKS
|
||||
} else {
|
||||
ShieldFundsLockoutDuration.ONE_MONTH
|
||||
},
|
||||
nextLockoutDuration = ShieldFundsLockoutDuration.ONE_MONTH
|
||||
)
|
||||
else ->
|
||||
calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration =
|
||||
if (count == 2) {
|
||||
ShieldFundsLockoutDuration.TWO_WEEKS
|
||||
} else {
|
||||
ShieldFundsLockoutDuration.ONE_MONTH
|
||||
},
|
||||
nextLockoutDuration = ShieldFundsLockoutDuration.ONE_MONTH
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remindMeLater(forAccount: AccountUuid) {
|
||||
val count = shieldFundsRemindMeCountStorageProvider.get(forAccount)
|
||||
|
@ -83,11 +85,17 @@ class ShieldFundsDataSourceImpl(
|
|||
}
|
||||
|
||||
sealed interface ShieldFundsAvailability {
|
||||
data class Available(val lockoutDuration: ShieldFundsLockoutDuration) : ShieldFundsAvailability
|
||||
data class Available(
|
||||
val lockoutDuration: ShieldFundsLockoutDuration
|
||||
) : 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_WEEKS(2.days, R.string.general_remind_me_in_two_weeks),
|
||||
ONE_MONTH(30.days, R.string.general_remind_me_in_two_months)
|
||||
|
|
|
@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.flowOf
|
|||
import java.time.Instant
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
interface WalletBackupDataSource {
|
||||
fun observe(): Flow<WalletBackupAvailability>
|
||||
|
@ -30,35 +29,38 @@ class WalletBackupDataSourceImpl(
|
|||
private val walletBackupRemindMeCountStorageProvider: WalletBackupRemindMeCountStorageProvider,
|
||||
private val walletBackupRemindMeTimestampStorageProvider: WalletBackupRemindMeTimestampStorageProvider
|
||||
) : WalletBackupDataSource {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun observe(): Flow<WalletBackupAvailability> = combine(
|
||||
walletBackupFlagStorageProvider.observe(),
|
||||
walletBackupRemindMeCountStorageProvider.observe(),
|
||||
walletBackupRemindMeTimestampStorageProvider.observe()
|
||||
) { isBackedUp, count, timestamp ->
|
||||
Triple(isBackedUp, count, timestamp)
|
||||
}.flatMapLatest { (isBackedUp, count, timestamp) ->
|
||||
when {
|
||||
isBackedUp -> flowOf(WalletBackupAvailability.Unavailable)
|
||||
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS))
|
||||
count == 1 -> calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS,
|
||||
nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS
|
||||
)
|
||||
override fun observe(): Flow<WalletBackupAvailability> =
|
||||
combine(
|
||||
walletBackupFlagStorageProvider.observe(),
|
||||
walletBackupRemindMeCountStorageProvider.observe(),
|
||||
walletBackupRemindMeTimestampStorageProvider.observe()
|
||||
) { isBackedUp, count, timestamp ->
|
||||
Triple(isBackedUp, count, timestamp)
|
||||
}.flatMapLatest { (isBackedUp, count, timestamp) ->
|
||||
when {
|
||||
isBackedUp -> flowOf(WalletBackupAvailability.Unavailable)
|
||||
timestamp == null -> flowOf(WalletBackupAvailability.Available(WalletBackupLockoutDuration.TWO_DAYS))
|
||||
count == 1 ->
|
||||
calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = WalletBackupLockoutDuration.TWO_DAYS,
|
||||
nextLockoutDuration = WalletBackupLockoutDuration.TWO_WEEKS
|
||||
)
|
||||
|
||||
else -> calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration = if (count == 2) {
|
||||
WalletBackupLockoutDuration.TWO_WEEKS
|
||||
} else {
|
||||
WalletBackupLockoutDuration.ONE_MONTH
|
||||
},
|
||||
nextLockoutDuration = WalletBackupLockoutDuration.ONE_MONTH
|
||||
)
|
||||
else ->
|
||||
calculateNext(
|
||||
lastTimestamp = timestamp,
|
||||
lastLockoutDuration =
|
||||
if (count == 2) {
|
||||
WalletBackupLockoutDuration.TWO_WEEKS
|
||||
} else {
|
||||
WalletBackupLockoutDuration.ONE_MONTH
|
||||
},
|
||||
nextLockoutDuration = WalletBackupLockoutDuration.ONE_MONTH
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onUserSavedWalletBackup() {
|
||||
walletBackupFlagStorageProvider.store(true)
|
||||
|
@ -92,11 +94,17 @@ class WalletBackupDataSourceImpl(
|
|||
}
|
||||
|
||||
sealed interface WalletBackupAvailability {
|
||||
data class Available(val lockoutDuration: WalletBackupLockoutDuration) : WalletBackupAvailability
|
||||
data class Available(
|
||||
val lockoutDuration: WalletBackupLockoutDuration
|
||||
) : 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_WEEKS(14.days, R.string.general_remind_me_in_two_weeks),
|
||||
ONE_MONTH(30.days, R.string.general_remind_me_in_two_months),
|
||||
|
|
|
@ -30,38 +30,37 @@ class WalletSnapshotDataSourceImpl(
|
|||
synchronizerProvider: SynchronizerProvider,
|
||||
walletRestoringStateProvider: WalletRestoringStateProvider,
|
||||
) : WalletSnapshotDataSource {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val flow = synchronizerProvider
|
||||
.synchronizer
|
||||
.flatMapLatest { synchronizer ->
|
||||
if (synchronizer == null) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
combine(
|
||||
synchronizer.status,
|
||||
synchronizer.progress,
|
||||
synchronizer.toCommonError(),
|
||||
synchronizer.areFundsSpendable,
|
||||
walletRestoringStateProvider.observe()
|
||||
) { status, progress, error, isSpendable, restoringState ->
|
||||
WalletSnapshot(
|
||||
status = status,
|
||||
progress = progress,
|
||||
synchronizerError = error,
|
||||
isSpendable = isSpendable,
|
||||
restoringState = restoringState,
|
||||
)
|
||||
val flow =
|
||||
synchronizerProvider
|
||||
.synchronizer
|
||||
.flatMapLatest { synchronizer ->
|
||||
if (synchronizer == null) {
|
||||
flowOf(null)
|
||||
} else {
|
||||
combine(
|
||||
synchronizer.status,
|
||||
synchronizer.progress,
|
||||
synchronizer.toCommonError(),
|
||||
synchronizer.areFundsSpendable,
|
||||
walletRestoringStateProvider.observe()
|
||||
) { status, progress, error, isSpendable, restoringState ->
|
||||
WalletSnapshot(
|
||||
status = status,
|
||||
progress = progress,
|
||||
synchronizerError = error,
|
||||
isSpendable = isSpendable,
|
||||
restoringState = restoringState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
override fun observe(): StateFlow<WalletSnapshot?> = flow
|
||||
|
||||
|
|
|
@ -53,17 +53,10 @@ sealed interface WalletAccount : Comparable<WalletAccount> {
|
|||
val isShieldedPending: Boolean
|
||||
get() = pendingShieldedBalance > Zatoshi(0)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
val isShieldingAvailable: Boolean
|
||||
get() = totalTransparentBalance > Zatoshi(100000L)
|
||||
|
||||
val isProcessingZeroSpendableBalance: Boolean
|
||||
get() {
|
||||
if (totalShieldedBalance == Zatoshi(0) && totalTransparentBalance > Zatoshi(0)) {
|
||||
return false
|
||||
}
|
||||
return totalBalance > Zatoshi(0) && totalShieldedBalance == Zatoshi(0)
|
||||
}
|
||||
|
||||
fun canSpend(amount: Zatoshi): Boolean = spendableShieldedBalance >= amount
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package co.electriccoin.zcash.ui.common.model
|
||||
|
||||
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.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
|
||||
|
||||
// TODO [#292]: Should be moved to SDK-EXT-UI module.
|
||||
|
|
|
@ -5,6 +5,9 @@ import co.electriccoin.zcash.preference.model.entry.PreferenceKey
|
|||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ import co.electriccoin.zcash.preference.model.entry.PreferenceKey
|
|||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ interface RestoreTimestampStorageProvider : TimestampStorageProvider
|
|||
|
||||
class RestoreTimestampStorageProviderImpl(
|
||||
override val preferenceHolder: EncryptedPreferenceProvider
|
||||
) : BaseTimestampStorageProvider(PreferenceKey("restore_timestamp")), RestoreTimestampStorageProvider
|
||||
) : BaseTimestampStorageProvider(PreferenceKey("restore_timestamp")),
|
||||
RestoreTimestampStorageProvider
|
||||
|
|
|
@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui.common.provider
|
|||
|
||||
import cash.z.ecc.android.sdk.model.AccountUuid
|
||||
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.PreferenceKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -18,22 +17,21 @@ interface ShieldFundsRemindMeCountStorageProvider {
|
|||
class ShieldFundsRemindMeCountStorageProviderImpl(
|
||||
private val encryptedPreferenceProvider: EncryptedPreferenceProvider
|
||||
) : ShieldFundsRemindMeCountStorageProvider {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun getDefault(forAccount: AccountUuid): IntegerPreferenceDefault {
|
||||
val key = PreferenceKey("shield_funds_remind_me_count_${forAccount.value.toHexString()}")
|
||||
return IntegerPreferenceDefault(key = key, defaultValue = 0)
|
||||
}
|
||||
|
||||
override suspend fun get(forAccount: AccountUuid): Int {
|
||||
return getDefault(forAccount).getValue(encryptedPreferenceProvider())
|
||||
}
|
||||
override suspend fun get(forAccount: AccountUuid): Int =
|
||||
getDefault(forAccount)
|
||||
.getValue(encryptedPreferenceProvider())
|
||||
|
||||
override suspend fun store(forAccount: AccountUuid, amount: Int) {
|
||||
getDefault(forAccount).putValue(encryptedPreferenceProvider(), amount)
|
||||
}
|
||||
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<Int> {
|
||||
return getDefault(forAccount).observe(encryptedPreferenceProvider())
|
||||
}
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<Int> =
|
||||
getDefault(forAccount)
|
||||
.observe(encryptedPreferenceProvider())
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package co.electriccoin.zcash.ui.common.provider
|
|||
|
||||
import cash.z.ecc.android.sdk.model.AccountUuid
|
||||
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.TimestampPreferenceDefault
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.Instant
|
||||
|
||||
|
@ -17,22 +17,20 @@ interface ShieldFundsRemindMeTimestampStorageProvider {
|
|||
|
||||
class ShieldFundsRemindMeTimestampStorageProviderImpl(
|
||||
private val encryptedPreferenceProvider: EncryptedPreferenceProvider
|
||||
) : ShieldFundsRemindMeTimestampStorageProvider {
|
||||
) : ShieldFundsRemindMeTimestampStorageProvider {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun getDefault(forAccount: AccountUuid): TimestampPreferenceDefault {
|
||||
val key = PreferenceKey("shield_funds_remind_me_timestamp_${forAccount.value.toHexString()}")
|
||||
return TimestampPreferenceDefault(key = key)
|
||||
}
|
||||
|
||||
override suspend fun get(forAccount: AccountUuid): Instant? {
|
||||
return getDefault(forAccount).getValue(encryptedPreferenceProvider())
|
||||
}
|
||||
override suspend fun get(forAccount: AccountUuid) = getDefault(forAccount).getValue(encryptedPreferenceProvider())
|
||||
|
||||
override suspend fun store(forAccount: AccountUuid, timestamp: Instant) {
|
||||
getDefault(forAccount).putValue(encryptedPreferenceProvider(), timestamp)
|
||||
}
|
||||
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<Instant?> {
|
||||
return getDefault(forAccount).observe(encryptedPreferenceProvider())
|
||||
}
|
||||
override suspend fun observe(forAccount: AccountUuid): Flow<Instant?> =
|
||||
getDefault(forAccount)
|
||||
.observe(encryptedPreferenceProvider())
|
||||
}
|
||||
|
|
|
@ -58,4 +58,3 @@ abstract class BaseNullableStorageProvider<T : Any> : NullableStorageProvider<T>
|
|||
|
||||
private suspend fun getPreferenceProvider(): PreferenceProvider = preferenceHolder()
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
@ -18,7 +17,6 @@ import kotlinx.coroutines.flow.flow
|
|||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.time.Duration
|
||||
|
||||
interface SynchronizerProvider {
|
||||
val synchronizer: StateFlow<Synchronizer?>
|
||||
|
|
|
@ -8,6 +8,7 @@ interface TimestampStorageProvider : NullableStorageProvider<Instant>
|
|||
|
||||
abstract class BaseTimestampStorageProvider(
|
||||
key: PreferenceKey
|
||||
) : BaseNullableStorageProvider<Instant>(), TimestampStorageProvider {
|
||||
) : BaseNullableStorageProvider<Instant>(),
|
||||
TimestampStorageProvider {
|
||||
override val default = TimestampPreferenceDefault(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ interface WalletBackupConsentStorageProvider : BooleanStorageProvider
|
|||
|
||||
class WalletBackupConsentStorageProviderImpl(
|
||||
override val preferenceHolder: EncryptedPreferenceProvider
|
||||
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_consent")), WalletBackupConsentStorageProvider
|
||||
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_consent")),
|
||||
WalletBackupConsentStorageProvider
|
||||
|
|
|
@ -7,4 +7,5 @@ interface WalletBackupFlagStorageProvider : BooleanStorageProvider
|
|||
|
||||
class WalletBackupFlagStorageProviderImpl(
|
||||
override val preferenceHolder: EncryptedPreferenceProvider
|
||||
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_flag")), WalletBackupFlagStorageProvider
|
||||
) : BaseBooleanStorageProvider(key = PreferenceKey("wallet_backup_flag")),
|
||||
WalletBackupFlagStorageProvider
|
||||
|
|
|
@ -11,12 +11,12 @@ interface WalletRestoringStateProvider : StorageProvider<WalletRestoringState>
|
|||
|
||||
class WalletRestoringStateProviderImpl(
|
||||
override val preferenceHolder: StandardPreferenceProvider,
|
||||
) : BaseStorageProvider<WalletRestoringState>(), WalletRestoringStateProvider {
|
||||
) : BaseStorageProvider<WalletRestoringState>(),
|
||||
WalletRestoringStateProvider {
|
||||
override val default: PreferenceDefault<WalletRestoringState> = WalletRestoringStatePreferenceDefault()
|
||||
}
|
||||
|
||||
private class WalletRestoringStatePreferenceDefault : PreferenceDefault<WalletRestoringState> {
|
||||
|
||||
private val internal = StandardPreferenceKeys.WALLET_RESTORING_STATE
|
||||
|
||||
override val key: PreferenceKey = internal.key
|
||||
|
|
|
@ -44,7 +44,6 @@ class ExchangeRateRepositoryImpl(
|
|||
private val synchronizerProvider: SynchronizerProvider,
|
||||
private val standardPreferenceProvider: StandardPreferenceProvider,
|
||||
) : ExchangeRateRepository {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
private val isExchangeRateUsdOptedIn = nullableBooleanStateFlow(StandardPreferenceKeys.EXCHANGE_RATE_OPTED_IN)
|
||||
|
|
|
@ -28,7 +28,6 @@ interface HomeMessageCacheRepository {
|
|||
class HomeMessageCacheRepositoryImpl(
|
||||
private val messageAvailabilityDataSource: MessageAvailabilityDataSource
|
||||
) : HomeMessageCacheRepository {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
override var lastShownMessage: HomeMessageData? = null
|
||||
|
@ -42,8 +41,7 @@ class HomeMessageCacheRepositoryImpl(
|
|||
lastShownMessage = null
|
||||
lastMessage = null
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
|
@ -52,21 +50,33 @@ class HomeMessageCacheRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
sealed interface HomeMessageData {
|
||||
|
||||
val priority: Int
|
||||
|
||||
data class Error(val synchronizerError: SynchronizerError) : RuntimeMessage()
|
||||
data class Error(
|
||||
val synchronizerError: SynchronizerError
|
||||
) : 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 Backup : Prioritized {
|
||||
override val priority: Int = 4
|
||||
}
|
||||
|
||||
data class ShieldFunds(val zatoshi: Zatoshi) : Prioritized {
|
||||
data class ShieldFunds(
|
||||
val zatoshi: Zatoshi
|
||||
) : Prioritized {
|
||||
override val priority: Int = 3
|
||||
}
|
||||
|
||||
|
@ -82,6 +92,7 @@ sealed interface HomeMessageData {
|
|||
/**
|
||||
* Message which always is shown.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
sealed class RuntimeMessage : HomeMessageData {
|
||||
override val priority: Int = 5
|
||||
}
|
||||
|
@ -89,4 +100,4 @@ sealed class RuntimeMessage : HomeMessageData {
|
|||
/**
|
||||
* Message which always is displayed only if previous message was lower priority.
|
||||
*/
|
||||
sealed interface Prioritized : HomeMessageData
|
||||
sealed interface Prioritized : HomeMessageData
|
||||
|
|
|
@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface ShieldFundsRepository {
|
||||
val availability: Flow<ShieldFundsData>
|
||||
|
@ -25,31 +24,33 @@ class ShieldFundsRepositoryImpl(
|
|||
private val messageAvailabilityDataSource: MessageAvailabilityDataSource,
|
||||
) : ShieldFundsRepository {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val availability: Flow<ShieldFundsData> = accountDataSource
|
||||
.selectedAccount
|
||||
.flatMapLatest { account ->
|
||||
when {
|
||||
account == null -> flowOf(ShieldFundsData.Unavailable)
|
||||
override val availability: Flow<ShieldFundsData> =
|
||||
accountDataSource
|
||||
.selectedAccount
|
||||
.flatMapLatest { account ->
|
||||
when {
|
||||
account == null -> flowOf(ShieldFundsData.Unavailable)
|
||||
|
||||
account.isShieldingAvailable ->
|
||||
combine(
|
||||
messageAvailabilityDataSource.canShowShieldMessage,
|
||||
shieldFundsDataSource.observe(account.sdkAccount.accountUuid)
|
||||
) { canShowShieldMessage, availability ->
|
||||
when {
|
||||
!canShowShieldMessage -> ShieldFundsData.Unavailable
|
||||
availability is ShieldFundsAvailability.Available -> ShieldFundsData.Available(
|
||||
lockoutDuration = availability.lockoutDuration,
|
||||
amount = account.transparent.balance
|
||||
)
|
||||
account.isShieldingAvailable ->
|
||||
combine(
|
||||
messageAvailabilityDataSource.canShowShieldMessage,
|
||||
shieldFundsDataSource.observe(account.sdkAccount.accountUuid)
|
||||
) { canShowShieldMessage, availability ->
|
||||
when {
|
||||
!canShowShieldMessage -> ShieldFundsData.Unavailable
|
||||
availability is ShieldFundsAvailability.Available ->
|
||||
ShieldFundsData.Available(
|
||||
lockoutDuration = availability.lockoutDuration,
|
||||
amount = account.transparent.balance
|
||||
)
|
||||
|
||||
else -> ShieldFundsData.Unavailable
|
||||
else -> ShieldFundsData.Unavailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> flowOf(ShieldFundsData.Unavailable)
|
||||
else -> flowOf(ShieldFundsData.Unavailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remindMeLater() {
|
||||
shieldFundsDataSource.remindMeLater(
|
||||
|
|
|
@ -73,23 +73,25 @@ class TransactionRepositoryImpl(
|
|||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val currentTransactions: Flow<List<Transaction>?> = accountDataSource.selectedAccount
|
||||
.distinctUntilChangedBy { it?.sdkAccount?.accountUuid }
|
||||
.flatMapLatest { selected ->
|
||||
if (selected is ZashiAccount) {
|
||||
zashiTransactions
|
||||
} else {
|
||||
observeTransactions(
|
||||
accountFlow = accountDataSource.selectedAccount.map { it?.sdkAccount?.accountUuid }
|
||||
.distinctUntilChanged()
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(5.seconds, Duration.ZERO),
|
||||
initialValue = null
|
||||
)
|
||||
override val currentTransactions: Flow<List<Transaction>?> =
|
||||
accountDataSource.selectedAccount
|
||||
.distinctUntilChangedBy { it?.sdkAccount?.accountUuid }
|
||||
.flatMapLatest { selected ->
|
||||
if (selected is ZashiAccount) {
|
||||
zashiTransactions
|
||||
} else {
|
||||
observeTransactions(
|
||||
accountFlow =
|
||||
accountDataSource.selectedAccount
|
||||
.map { it?.sdkAccount?.accountUuid }
|
||||
.distinctUntilChanged()
|
||||
)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(5.seconds, Duration.ZERO),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun TransactionRepositoryImpl.observeTransactions(accountFlow: Flow<AccountUuid?>) =
|
||||
|
@ -123,6 +125,7 @@ class TransactionRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun createTransactions(
|
||||
transactions: List<TransactionOverview>,
|
||||
synchronizer: Synchronizer
|
||||
|
|
|
@ -6,7 +6,6 @@ import cash.z.ecc.android.sdk.Synchronizer
|
|||
import cash.z.ecc.android.sdk.WalletInitMode
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
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.SeedPhrase
|
||||
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.zcash.preference.EncryptedPreferenceProvider
|
||||
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.RestoreTimestampDataSource
|
||||
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.WalletRestoringStateProvider
|
||||
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.StandardPreferenceKeys
|
||||
import co.electriccoin.zcash.ui.screen.chooseserver.AvailableServerProvider
|
||||
|
@ -36,13 +33,11 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
@ -51,7 +46,6 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -196,13 +190,14 @@ class WalletRepositoryImpl(
|
|||
/**
|
||||
* A flow of the wallet block synchronization state.
|
||||
*/
|
||||
override val walletRestoringState: StateFlow<WalletRestoringState> = walletRestoringStateProvider
|
||||
.observe()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = WalletRestoringState.NONE
|
||||
)
|
||||
override val walletRestoringState: StateFlow<WalletRestoringState> =
|
||||
walletRestoringStateProvider
|
||||
.observe()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = WalletRestoringState.NONE
|
||||
)
|
||||
|
||||
/**
|
||||
* Persists a wallet asynchronously. Clients observe [secretState] to see the side effects.
|
||||
|
|
|
@ -23,7 +23,6 @@ class WalletSnapshotRepositoryImpl(
|
|||
private val synchronizerProvider: SynchronizerProvider,
|
||||
private val walletRestoringStateProvider: WalletRestoringStateProvider
|
||||
) : WalletSnapshotRepository {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
@ -38,8 +37,7 @@ class WalletSnapshotRepositoryImpl(
|
|||
status to restoringState
|
||||
}
|
||||
}
|
||||
}
|
||||
.collect { (status, restoringState) ->
|
||||
}.collect { (status, restoringState) ->
|
||||
// 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)) {
|
||||
walletRestoringStateProvider.store(WalletRestoringState.SYNCING)
|
||||
|
|
|
@ -43,10 +43,11 @@ class CreateFlexaTransactionUseCase(
|
|||
)
|
||||
}.onSuccess { proposal ->
|
||||
Twig.debug { "Transaction proposal successful: ${proposal.toPrettyString()}" }
|
||||
val result = submitTransactions(
|
||||
proposal = proposal,
|
||||
spendingKey = zashiSpendingKeyDataSource.getZashiSpendingKey()
|
||||
)
|
||||
val result =
|
||||
submitTransactions(
|
||||
proposal = proposal,
|
||||
spendingKey = zashiSpendingKeyDataSource.getZashiSpendingKey()
|
||||
)
|
||||
when (val output = result.first) {
|
||||
is SubmitResult.Success -> {
|
||||
Twig.debug { "Transaction successful $result" }
|
||||
|
|
|
@ -38,80 +38,89 @@ class GetHomeMessageUseCase(
|
|||
private val messageAvailabilityDataSource: MessageAvailabilityDataSource,
|
||||
private val cache: HomeMessageCacheRepository,
|
||||
) {
|
||||
private val backupFlow = combine(
|
||||
transactionRepository.zashiTransactions,
|
||||
walletBackupDataSource.observe()
|
||||
) { transactions, backup ->
|
||||
if (backup is WalletBackupAvailability.Available && transactions.orEmpty().any { it is ReceiveTransaction }) {
|
||||
backup
|
||||
} else {
|
||||
WalletBackupAvailability.Unavailable
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
private val backupFlow =
|
||||
combine(
|
||||
transactionRepository.zashiTransactions,
|
||||
walletBackupDataSource.observe()
|
||||
) { transactions, backup ->
|
||||
if (backup is WalletBackupAvailability.Available &&
|
||||
transactions.orEmpty().any { it is ReceiveTransaction }
|
||||
) {
|
||||
backup
|
||||
} else {
|
||||
WalletBackupAvailability.Unavailable
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
private val runtimeMessage = channelFlow {
|
||||
var firstSyncing: WalletSnapshot? = null
|
||||
launch {
|
||||
walletSnapshotDataSource
|
||||
.observe()
|
||||
.filterNotNull()
|
||||
.collect { walletSnapshot ->
|
||||
val result = when {
|
||||
walletSnapshot.synchronizerError != null ->
|
||||
HomeMessageData.Error(walletSnapshot.synchronizerError)
|
||||
@Suppress("MagicNumber")
|
||||
private val runtimeMessage =
|
||||
channelFlow {
|
||||
var firstSyncing: WalletSnapshot? = null
|
||||
launch {
|
||||
walletSnapshotDataSource
|
||||
.observe()
|
||||
.filterNotNull()
|
||||
.collect { walletSnapshot ->
|
||||
val result =
|
||||
when {
|
||||
walletSnapshot.synchronizerError != null ->
|
||||
HomeMessageData.Error(walletSnapshot.synchronizerError)
|
||||
|
||||
walletSnapshot.status == Synchronizer.Status.DISCONNECTED ->
|
||||
HomeMessageData.Disconnected
|
||||
walletSnapshot.status == Synchronizer.Status.DISCONNECTED ->
|
||||
HomeMessageData.Disconnected
|
||||
|
||||
walletSnapshot.status in listOf(
|
||||
Synchronizer.Status.INITIALIZING,
|
||||
Synchronizer.Status.SYNCING,
|
||||
Synchronizer.Status.STOPPED
|
||||
) -> {
|
||||
val progress = walletSnapshot.progress.decimal * 100f
|
||||
if (walletSnapshot.restoringState == WalletRestoringState.RESTORING) {
|
||||
HomeMessageData.Restoring(progress = progress)
|
||||
} else {
|
||||
HomeMessageData.Syncing(progress = progress)
|
||||
walletSnapshot.status in
|
||||
listOf(
|
||||
Synchronizer.Status.INITIALIZING,
|
||||
Synchronizer.Status.SYNCING,
|
||||
Synchronizer.Status.STOPPED
|
||||
)
|
||||
-> {
|
||||
val progress = walletSnapshot.progress.decimal * 100f
|
||||
if (walletSnapshot.restoringState == WalletRestoringState.RESTORING) {
|
||||
HomeMessageData.Restoring(progress = progress)
|
||||
} else {
|
||||
HomeMessageData.Syncing(progress = progress)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
if (result is HomeMessageData.Syncing) {
|
||||
if (firstSyncing == null) {
|
||||
firstSyncing = walletSnapshot
|
||||
}
|
||||
|
||||
if (result is HomeMessageData.Syncing) {
|
||||
if (firstSyncing == null) {
|
||||
firstSyncing = walletSnapshot
|
||||
}
|
||||
|
||||
if ((firstSyncing?.progress?.decimal ?: 0f) >= .95f) {
|
||||
send(null)
|
||||
if ((firstSyncing?.progress?.decimal ?: 0f) >= .95f) {
|
||||
send(null)
|
||||
} else {
|
||||
send(result)
|
||||
}
|
||||
} else {
|
||||
firstSyncing = null
|
||||
send(result)
|
||||
}
|
||||
} else {
|
||||
firstSyncing = null
|
||||
send(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
// do nothing
|
||||
awaitClose {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private val flow = combine(
|
||||
runtimeMessage,
|
||||
backupFlow,
|
||||
exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(),
|
||||
shieldFundsRepository.availability
|
||||
) { runtimeMessage, backup, isCCAvailable, shieldFunds ->
|
||||
createMessage(runtimeMessage, backup, shieldFunds, isCCAvailable)
|
||||
}.distinctUntilChanged()
|
||||
.debounce(.5.seconds)
|
||||
.map { message -> prioritizeMessage(message) }
|
||||
private val flow =
|
||||
combine(
|
||||
runtimeMessage,
|
||||
backupFlow,
|
||||
exchangeRateRepository.state.map { it == ExchangeRateState.OptIn }.distinctUntilChanged(),
|
||||
shieldFundsRepository.availability
|
||||
) { runtimeMessage, backup, isCCAvailable, shieldFunds ->
|
||||
createMessage(runtimeMessage, backup, shieldFunds, isCCAvailable)
|
||||
}.distinctUntilChanged()
|
||||
.debounce(.5.seconds)
|
||||
.map { message -> prioritizeMessage(message) }
|
||||
|
||||
fun observe(): Flow<HomeMessageData?> = flow
|
||||
|
||||
|
@ -133,18 +142,20 @@ class GetHomeMessageUseCase(
|
|||
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 isHigherPriorityMessage = (message?.priority ?: 0) > (cache.lastShownMessage?.priority ?: 0)
|
||||
val result = when {
|
||||
message == null -> null
|
||||
message is RuntimeMessage -> message
|
||||
isSameMessageUpdate -> message
|
||||
isHigherPriorityMessage -> if (hasNoMessageBeenShownLately) {
|
||||
if (someMessageBeenShown) null else message
|
||||
} else {
|
||||
message
|
||||
}
|
||||
val result =
|
||||
when {
|
||||
message == null -> null
|
||||
message is RuntimeMessage -> message
|
||||
isSameMessageUpdate -> message
|
||||
isHigherPriorityMessage ->
|
||||
if (hasNoMessageBeenShownLately) {
|
||||
if (someMessageBeenShown) null else message
|
||||
} else {
|
||||
message
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
messageAvailabilityDataSource.onMessageShown()
|
||||
|
|
|
@ -29,8 +29,19 @@ class NavigateToErrorUseCase(
|
|||
}
|
||||
|
||||
sealed interface ErrorArgs {
|
||||
data class SyncError(val synchronizerError: SynchronizerError) : ErrorArgs
|
||||
data class ShieldingError(val error: SubmitResult.Failure): ErrorArgs
|
||||
data class ShieldingGeneralError(val exception: Exception): ErrorArgs
|
||||
data class General(val exception: Exception): ErrorArgs
|
||||
data class SyncError(
|
||||
val synchronizerError: SynchronizerError
|
||||
) : ErrorArgs
|
||||
|
||||
data class ShieldingError(
|
||||
val error: SubmitResult.Failure
|
||||
) : ErrorArgs
|
||||
|
||||
data class ShieldingGeneralError(
|
||||
val exception: Exception
|
||||
) : ErrorArgs
|
||||
|
||||
data class General(
|
||||
val exception: Exception
|
||||
) : ErrorArgs
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@ class OnUserSavedWalletBackupUseCase(
|
|||
walletBackupDataSource.onUserSavedWalletBackup()
|
||||
navigationRouter.backToRoot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import co.electriccoin.zcash.ui.NavigationRouter
|
||||
import co.electriccoin.zcash.ui.common.datasource.WalletBackupDataSource
|
||||
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
|
||||
|
||||
class RemindShieldFundsLaterUseCase(
|
||||
|
@ -12,4 +11,4 @@ class RemindShieldFundsLaterUseCase(
|
|||
shieldFundsRepository.remindMeLater()
|
||||
navigationRouter.backToRoot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,4 @@ class RemindWalletBackupLaterUseCase(
|
|||
walletBackupDataSource.remindMeLater()
|
||||
navigationRouter.backToRoot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ class SendEmailUseCase(
|
|||
runCatching { context.startActivity(mailIntent) }
|
||||
}
|
||||
|
||||
|
||||
suspend operator fun invoke(synchronizerError: SynchronizerError) {
|
||||
val fullMessage =
|
||||
EmailUtil.formatMessage(
|
||||
|
|
|
@ -46,14 +46,16 @@ class ShieldFundsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private suspend fun shieldZashiFunds() {
|
||||
try {
|
||||
zashiProposalRepository.createShieldProposal()
|
||||
zashiProposalRepository.submitTransaction()
|
||||
val result = zashiProposalRepository.submitState
|
||||
.filterIsInstance<SubmitProposalState.Result>()
|
||||
.first()
|
||||
.submitResult
|
||||
val result =
|
||||
zashiProposalRepository.submitState
|
||||
.filterIsInstance<SubmitProposalState.Result>()
|
||||
.first()
|
||||
.submitResult
|
||||
|
||||
if (result is SubmitResult.Failure) {
|
||||
navigateToError(ErrorArgs.ShieldingError(result))
|
||||
|
@ -65,6 +67,7 @@ class ShieldFundsUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private suspend fun createKeystoneShieldProposal() {
|
||||
try {
|
||||
keystoneProposalRepository.createShieldProposal()
|
||||
|
@ -75,4 +78,4 @@ class ShieldFundsUseCase(
|
|||
navigateToError(ErrorArgs.ShieldingGeneralError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import co.electriccoin.zcash.ui.NavigationTargets
|
|||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
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.NavigateToWalletBackupUseCase
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.listitem.ZashiListItemState
|
||||
import co.electriccoin.zcash.ui.design.util.stringRes
|
||||
|
|
|
@ -103,12 +103,13 @@ private fun BalanceWidgetPreview() {
|
|||
state =
|
||||
BalanceWidgetState(
|
||||
totalBalance = Zatoshi(1234567891234567L),
|
||||
button = BalanceButtonState(
|
||||
icon = R.drawable.ic_help,
|
||||
text = stringRes("text"),
|
||||
amount = Zatoshi(1000),
|
||||
onClick = {}
|
||||
),
|
||||
button =
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_help,
|
||||
text = stringRes("text"),
|
||||
amount = Zatoshi(1000),
|
||||
onClick = {}
|
||||
),
|
||||
exchangeRate = ObserveFiatCurrencyResultFixture.new(),
|
||||
showDust = true
|
||||
),
|
||||
|
|
|
@ -41,10 +41,11 @@ internal fun BalanceWidgetButton(
|
|||
state: BalanceButtonState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = ZashiButtonDefaults.secondaryColors(
|
||||
containerColor = ZashiColors.Surfaces.bgPrimary,
|
||||
borderColor = ZashiColors.Utility.Gray.utilityGray100
|
||||
)
|
||||
val colors =
|
||||
ZashiButtonDefaults.secondaryColors(
|
||||
containerColor = ZashiColors.Surfaces.bgPrimary,
|
||||
borderColor = ZashiColors.Utility.Gray.utilityGray100
|
||||
)
|
||||
val borderColor = colors.borderColor
|
||||
|
||||
Button(
|
||||
|
@ -53,10 +54,11 @@ internal fun BalanceWidgetButton(
|
|||
shape = RoundedCornerShape(ZashiDimensions.Radius.radiusIg),
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 10.dp),
|
||||
colors = colors.toButtonColors(),
|
||||
elevation = ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 1.dp,
|
||||
pressedElevation = 0.dp
|
||||
),
|
||||
elevation =
|
||||
ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 1.dp,
|
||||
pressedElevation = 0.dp
|
||||
),
|
||||
border = borderColor.takeIf { it != Color.Unspecified }?.let { BorderStroke(1.dp, it) },
|
||||
content = {
|
||||
Row(
|
||||
|
@ -110,15 +112,17 @@ data class BalanceButtonState(
|
|||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() = ZcashTheme {
|
||||
BlankSurface {
|
||||
BalanceWidgetButton(
|
||||
state = BalanceButtonState(
|
||||
icon = R.drawable.ic_help,
|
||||
text = stringRes("text"),
|
||||
amount = Zatoshi(1000),
|
||||
onClick = {}
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
BalanceWidgetButton(
|
||||
state =
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_help,
|
||||
text = stringRes("text"),
|
||||
amount = Zatoshi(1000),
|
||||
onClick = {}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,54 +34,57 @@ class BalanceWidgetViewModel(
|
|||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = createState(
|
||||
account = accountDataSource.allAccounts.value?.firstOrNull { it.isSelected },
|
||||
exchangeRateUsd = exchangeRateRepository.state.value
|
||||
)
|
||||
initialValue =
|
||||
createState(
|
||||
account = accountDataSource.allAccounts.value?.firstOrNull { it.isSelected },
|
||||
exchangeRateUsd = exchangeRateRepository.state.value
|
||||
)
|
||||
)
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun createState(account: WalletAccount?, exchangeRateUsd: ExchangeRateState) =
|
||||
BalanceWidgetState(
|
||||
totalBalance = account?.totalBalance ?: Zatoshi(0),
|
||||
exchangeRate = if (args.isExchangeRateButtonEnabled) exchangeRateUsd else null,
|
||||
button = when {
|
||||
!args.isBalanceButtonEnabled -> null
|
||||
account == null -> null
|
||||
account.totalBalance == account.spendableShieldedBalance -> null
|
||||
account.totalBalance > account.spendableShieldedBalance &&
|
||||
!account.isShieldedPending &&
|
||||
account.totalShieldedBalance > Zatoshi(0) &&
|
||||
account.spendableShieldedBalance == Zatoshi(0) &&
|
||||
account.totalTransparentBalance > Zatoshi(0) ->
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = null,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
button =
|
||||
when {
|
||||
!args.isBalanceButtonEnabled -> null
|
||||
account == null -> null
|
||||
account.totalBalance == account.spendableShieldedBalance -> null
|
||||
account.totalBalance > account.spendableShieldedBalance &&
|
||||
!account.isShieldedPending &&
|
||||
account.totalShieldedBalance > Zatoshi(0) &&
|
||||
account.spendableShieldedBalance == Zatoshi(0) &&
|
||||
account.totalTransparentBalance > Zatoshi(0) ->
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = null,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
|
||||
account.totalBalance > account.spendableShieldedBalance &&
|
||||
account.isShieldedPending &&
|
||||
account.totalShieldedBalance > Zatoshi(0) &&
|
||||
account.spendableShieldedBalance == Zatoshi(0) &&
|
||||
account.totalTransparentBalance == Zatoshi(0) ->
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = null,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
account.totalBalance > account.spendableShieldedBalance &&
|
||||
account.isShieldedPending &&
|
||||
account.totalShieldedBalance > Zatoshi(0) &&
|
||||
account.spendableShieldedBalance == Zatoshi(0) &&
|
||||
account.totalTransparentBalance == Zatoshi(0) ->
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = null,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
|
||||
account.totalBalance > account.spendableShieldedBalance ->
|
||||
BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = account.spendableShieldedBalance,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
|
||||
account.totalBalance > account.spendableShieldedBalance -> BalanceButtonState(
|
||||
icon = R.drawable.ic_balances_expand,
|
||||
text = stringRes(R.string.widget_balances_button_spendable),
|
||||
amount = account.spendableShieldedBalance,
|
||||
onClick = ::onBalanceButtonClick
|
||||
)
|
||||
|
||||
else -> null
|
||||
},
|
||||
else -> null
|
||||
},
|
||||
showDust = args.showDust
|
||||
)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ data class BalanceActionState(
|
|||
val shieldButton: BalanceShieldButtonState?,
|
||||
val positive: ButtonState,
|
||||
override val onBack: () -> Unit,
|
||||
): ModalBottomSheetState
|
||||
) : ModalBottomSheetState
|
||||
|
||||
@Immutable
|
||||
data class BalanceActionRowState(
|
||||
|
|
|
@ -108,11 +108,12 @@ private fun BalanceActionRow(state: BalanceActionRowState) {
|
|||
)
|
||||
Spacer(1f)
|
||||
when (state.icon) {
|
||||
is ImageResource.ByDrawable -> Image(
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(state.icon.resource),
|
||||
contentDescription = null
|
||||
)
|
||||
is ImageResource.ByDrawable ->
|
||||
Image(
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(state.icon.resource),
|
||||
contentDescription = null
|
||||
)
|
||||
ImageResource.Loading -> LottieProgress(modifier = Modifier.size(20.dp))
|
||||
is ImageResource.DisplayString -> {
|
||||
// do nothing
|
||||
|
@ -122,11 +123,12 @@ private fun BalanceActionRow(state: BalanceActionRowState) {
|
|||
SelectionContainer {
|
||||
Text(
|
||||
text = state.value.getValue(),
|
||||
color = if (state.icon is ImageResource.Loading) {
|
||||
ZashiColors.Text.textTertiary
|
||||
} else {
|
||||
ZashiColors.Text.textPrimary
|
||||
},
|
||||
color =
|
||||
if (state.icon is ImageResource.Loading) {
|
||||
ZashiColors.Text.textTertiary
|
||||
} else {
|
||||
ZashiColors.Text.textPrimary
|
||||
},
|
||||
style = ZashiTypography.textSm,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
|
@ -176,9 +178,11 @@ private fun BalanceShieldButton(state: BalanceShieldButtonState) {
|
|||
}
|
||||
Spacer(1f)
|
||||
ZashiButton(
|
||||
state = ButtonState(
|
||||
text = stringRes(R.string.balance_action_shield),
|
||||
onClick = state.onShieldClick)
|
||||
state =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.balance_action_shield),
|
||||
onClick = state.onShieldClick
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -187,31 +191,36 @@ private fun BalanceShieldButton(state: BalanceShieldButtonState) {
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() = ZcashTheme {
|
||||
BalanceActionView(
|
||||
state = BalanceActionState(
|
||||
title = stringRes("Title"),
|
||||
message = stringRes("Subtitle"),
|
||||
positive = ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
onBack = {},
|
||||
rows = listOf(
|
||||
BalanceActionRowState(
|
||||
title = stringRes("Row"),
|
||||
icon = loadingImageRes(),
|
||||
value = stringRes("Value")
|
||||
),
|
||||
BalanceActionRowState(
|
||||
title = stringRes("Row"),
|
||||
icon = imageRes(R.drawable.ic_balance_shield),
|
||||
value = stringRes("Value")
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
BalanceActionView(
|
||||
state =
|
||||
BalanceActionState(
|
||||
title = stringRes("Title"),
|
||||
message = stringRes("Subtitle"),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
onBack = {},
|
||||
rows =
|
||||
listOf(
|
||||
BalanceActionRowState(
|
||||
title = stringRes("Row"),
|
||||
icon = loadingImageRes(),
|
||||
value = stringRes("Value")
|
||||
),
|
||||
BalanceActionRowState(
|
||||
title = stringRes("Row"),
|
||||
icon = imageRes(R.drawable.ic_balance_shield),
|
||||
value = stringRes("Value")
|
||||
)
|
||||
),
|
||||
shieldButton =
|
||||
BalanceShieldButtonState(
|
||||
amount = Zatoshi(10000),
|
||||
onShieldClick = {}
|
||||
)
|
||||
)
|
||||
),
|
||||
shieldButton = BalanceShieldButtonState(
|
||||
amount = Zatoshi(10000),
|
||||
onShieldClick = {}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,20 @@ class BalanceActionViewModel(
|
|||
private val navigationRouter: NavigationRouter,
|
||||
private val shieldFunds: ShieldFundsUseCase,
|
||||
) : ViewModel() {
|
||||
val state = accountDataSource.selectedAccount
|
||||
.mapNotNull {
|
||||
createState(it)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = createState(accountDataSource.allAccounts.value.orEmpty().firstOrNull { it.isSelected })
|
||||
)
|
||||
val state =
|
||||
accountDataSource.selectedAccount
|
||||
.mapNotNull {
|
||||
createState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue =
|
||||
createState(
|
||||
accountDataSource.allAccounts.value
|
||||
.orEmpty()
|
||||
.firstOrNull { it.isSelected }
|
||||
)
|
||||
)
|
||||
|
||||
private fun createState(account: WalletAccount?): BalanceActionState? {
|
||||
if (account == null) return null
|
||||
|
@ -47,15 +52,16 @@ class BalanceActionViewModel(
|
|||
}
|
||||
|
||||
private fun createMessage(account: WalletAccount): StringResource {
|
||||
val pending = when {
|
||||
account.totalShieldedBalance == account.spendableShieldedBalance ->
|
||||
stringRes(R.string.balance_action_all_shielded)
|
||||
val pending =
|
||||
when {
|
||||
account.totalShieldedBalance == account.spendableShieldedBalance ->
|
||||
stringRes(R.string.balance_action_all_shielded)
|
||||
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance ->
|
||||
stringRes(R.string.balance_action_pending)
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance ->
|
||||
stringRes(R.string.balance_action_pending)
|
||||
|
||||
else -> null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
val shielding = stringRes(R.string.balance_action_shield_message).takeIf { account.isShieldingAvailable }
|
||||
|
||||
|
@ -66,51 +72,54 @@ class BalanceActionViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createPositiveButton(account: WalletAccount) = ButtonState(
|
||||
text = if (account.isShieldingAvailable) {
|
||||
stringRes(R.string.general_dismiss)
|
||||
} else {
|
||||
stringRes(R.string.general_ok)
|
||||
},
|
||||
onClick = ::onBack
|
||||
)
|
||||
private fun createPositiveButton(account: WalletAccount) =
|
||||
ButtonState(
|
||||
text =
|
||||
if (account.isShieldingAvailable) {
|
||||
stringRes(R.string.general_dismiss)
|
||||
} else {
|
||||
stringRes(R.string.general_ok)
|
||||
},
|
||||
onClick = ::onBack
|
||||
)
|
||||
|
||||
private fun createInfoRows(account: WalletAccount) = listOfNotNull(
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_shielded),
|
||||
icon = imageRes(R.drawable.ic_balance_shield),
|
||||
value = stringRes(R.string.general_zec, stringRes(account.spendableShieldedBalance))
|
||||
),
|
||||
when {
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance && account.isShieldedPending ->
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_pending),
|
||||
icon = loadingImageRes(),
|
||||
value = stringRes(R.string.general_zec, stringRes(account.pendingShieldedBalance))
|
||||
)
|
||||
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance ->
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_pending),
|
||||
icon = loadingImageRes(),
|
||||
value = stringRes(
|
||||
R.string.general_zec,
|
||||
stringRes(account.totalShieldedBalance - account.spendableShieldedBalance)
|
||||
private fun createInfoRows(account: WalletAccount) =
|
||||
listOfNotNull(
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_shielded),
|
||||
icon = imageRes(R.drawable.ic_balance_shield),
|
||||
value = stringRes(R.string.general_zec, stringRes(account.spendableShieldedBalance))
|
||||
),
|
||||
when {
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance && account.isShieldedPending ->
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_pending),
|
||||
icon = loadingImageRes(),
|
||||
value = stringRes(R.string.general_zec, stringRes(account.pendingShieldedBalance))
|
||||
)
|
||||
)
|
||||
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
account.totalShieldedBalance > account.spendableShieldedBalance ->
|
||||
BalanceActionRowState(
|
||||
title = stringRes(R.string.balance_action_info_pending),
|
||||
icon = loadingImageRes(),
|
||||
value =
|
||||
stringRes(
|
||||
R.string.general_zec,
|
||||
stringRes(account.totalShieldedBalance - account.spendableShieldedBalance)
|
||||
)
|
||||
)
|
||||
|
||||
private fun createShieldButtonState(account: WalletAccount): BalanceShieldButtonState? {
|
||||
return BalanceShieldButtonState(
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
|
||||
private fun createShieldButtonState(account: WalletAccount): BalanceShieldButtonState? =
|
||||
BalanceShieldButtonState(
|
||||
amount = account.transparent.balance,
|
||||
onShieldClick = ::onShieldClick
|
||||
).takeIf { account.isShieldingAvailable }
|
||||
}
|
||||
|
||||
private fun onBack() = navigationRouter.back()
|
||||
|
||||
private fun onShieldClick() = shieldFunds(closeCurrentScreen = true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.material3.SnackbarHostState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
|
|
|
@ -85,18 +85,22 @@ fun BottomSheetContent(state: ErrorState, modifier: Modifier = Modifier) {
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() = ZcashTheme {
|
||||
BottomSheetErrorView(
|
||||
state = ErrorState(
|
||||
title = stringRes("Error"),
|
||||
message = stringRes("Something went wrong"),
|
||||
positive = ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes("Negative")
|
||||
),
|
||||
onBack = {}
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
BottomSheetErrorView(
|
||||
state =
|
||||
ErrorState(
|
||||
title = stringRes("Error"),
|
||||
message = stringRes("Something went wrong"),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes("Negative")
|
||||
),
|
||||
onBack = {}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,32 +11,37 @@ import co.electriccoin.zcash.ui.design.util.stringRes
|
|||
@Composable
|
||||
fun DialogView(state: ErrorState?) {
|
||||
ZashiScreenDialog(
|
||||
state = state?.let {
|
||||
DialogState(
|
||||
title = it.title,
|
||||
message = it.message,
|
||||
positive = it.positive,
|
||||
negative = it.negative,
|
||||
onDismissRequest = it.onBack
|
||||
)
|
||||
},
|
||||
state =
|
||||
state?.let {
|
||||
DialogState(
|
||||
title = it.title,
|
||||
message = it.message,
|
||||
positive = it.positive,
|
||||
negative = it.negative,
|
||||
onDismissRequest = it.onBack
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun Preview() = ZcashTheme {
|
||||
DialogView(
|
||||
state = ErrorState(
|
||||
title = stringRes("Error"),
|
||||
message = stringRes("Something went wrong"),
|
||||
positive = ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes("Negative")
|
||||
),
|
||||
onBack = {}
|
||||
private fun Preview() =
|
||||
ZcashTheme {
|
||||
DialogView(
|
||||
state =
|
||||
ErrorState(
|
||||
title = stringRes("Error"),
|
||||
message = stringRes("Something went wrong"),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes("Positive")
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes("Negative")
|
||||
),
|
||||
onBack = {}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,4 @@ data class ErrorState(
|
|||
val positive: ButtonState,
|
||||
val negative: ButtonState,
|
||||
override val onBack: () -> Unit
|
||||
): ModalBottomSheetState
|
||||
) : ModalBottomSheetState
|
||||
|
|
|
@ -33,96 +33,115 @@ class ErrorViewModel(
|
|||
|
||||
private fun onBack() = navigationRouter.back()
|
||||
|
||||
private fun createState(args: ErrorArgs): ErrorState = when (args) {
|
||||
is ErrorArgs.SyncError -> createSyncErrorState(args)
|
||||
is ErrorArgs.ShieldingError -> createShieldingErrorState(args)
|
||||
is ErrorArgs.General -> createGeneralErrorState(args)
|
||||
is ErrorArgs.ShieldingGeneralError -> createGeneralShieldingErrorState(args)
|
||||
}
|
||||
|
||||
private fun createSyncErrorState(args: ErrorArgs.SyncError) = ErrorState(
|
||||
title = stringRes(R.string.error_sync_title),
|
||||
message = stringRes(args.synchronizerError.getStackTrace(STACKTRACE_LIMIT).orEmpty()),
|
||||
positive = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun createShieldingErrorState(args: ErrorArgs.ShieldingError) = ErrorState(
|
||||
title = stringRes(R.string.error_shielding_title),
|
||||
message = when (args.error) {
|
||||
is SubmitResult.MultipleTrxFailure -> stringRes(R.string.error_shielding_message_grpc)
|
||||
is SubmitResult.SimpleTrxFailure -> stringRes(
|
||||
R.string.error_shielding_message,
|
||||
stringRes(args.error.toErrorStacktrace())
|
||||
)
|
||||
},
|
||||
positive = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun createGeneralErrorState(args: ErrorArgs.General) = ErrorState(
|
||||
title = stringRes(R.string.error_general_title),
|
||||
message = stringRes(R.string.error_general_message),
|
||||
positive = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args.exception) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun createGeneralShieldingErrorState(args: ErrorArgs.ShieldingGeneralError) = ErrorState(
|
||||
title = stringRes(R.string.error_shielding_title),
|
||||
message = stringRes(
|
||||
R.string.error_shielding_message,
|
||||
stringRes(args.exception.stackTraceToString().take(STACKTRACE_LIMIT))
|
||||
),
|
||||
positive = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative = ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args.exception) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun sendReportClick(args: ErrorArgs.ShieldingError) = viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(args.error)
|
||||
private fun createState(args: ErrorArgs): ErrorState =
|
||||
when (args) {
|
||||
is ErrorArgs.SyncError -> createSyncErrorState(args)
|
||||
is ErrorArgs.ShieldingError -> createShieldingErrorState(args)
|
||||
is ErrorArgs.General -> createGeneralErrorState(args)
|
||||
is ErrorArgs.ShieldingGeneralError -> createGeneralShieldingErrorState(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendReportClick(args: ErrorArgs.SyncError) = viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(args.synchronizerError)
|
||||
}
|
||||
}
|
||||
private fun createSyncErrorState(args: ErrorArgs.SyncError) =
|
||||
ErrorState(
|
||||
title = stringRes(R.string.error_sync_title),
|
||||
message = stringRes(args.synchronizerError.getStackTrace(STACKTRACE_LIMIT).orEmpty()),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun sendReportClick(exception: Exception) = viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(exception)
|
||||
private fun createShieldingErrorState(args: ErrorArgs.ShieldingError) =
|
||||
ErrorState(
|
||||
title = stringRes(R.string.error_shielding_title),
|
||||
message =
|
||||
when (args.error) {
|
||||
is SubmitResult.MultipleTrxFailure -> stringRes(R.string.error_shielding_message_grpc)
|
||||
is SubmitResult.SimpleTrxFailure ->
|
||||
stringRes(
|
||||
R.string.error_shielding_message,
|
||||
stringRes(args.error.toErrorStacktrace())
|
||||
)
|
||||
},
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun createGeneralErrorState(args: ErrorArgs.General) =
|
||||
ErrorState(
|
||||
title = stringRes(R.string.error_general_title),
|
||||
message = stringRes(R.string.error_general_message),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args.exception) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun createGeneralShieldingErrorState(args: ErrorArgs.ShieldingGeneralError) =
|
||||
ErrorState(
|
||||
title = stringRes(R.string.error_shielding_title),
|
||||
message =
|
||||
stringRes(
|
||||
R.string.error_shielding_message,
|
||||
stringRes(args.exception.stackTraceToString().take(STACKTRACE_LIMIT))
|
||||
),
|
||||
positive =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = { navigationRouter.back() }
|
||||
),
|
||||
negative =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_report),
|
||||
onClick = { sendReportClick(args.exception) }
|
||||
),
|
||||
onBack = ::onBack,
|
||||
)
|
||||
|
||||
private fun sendReportClick(args: ErrorArgs.ShieldingError) =
|
||||
viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(args.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendReportClick(args: ErrorArgs.SyncError) =
|
||||
viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(args.synchronizerError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendReportClick(exception: Exception) =
|
||||
viewModelScope.launch {
|
||||
withContext(NonCancellable) {
|
||||
navigationRouter.back()
|
||||
sendEmailUseCase(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,4 @@ data class ExchangeRateOptInState(
|
|||
val onEnableClick: () -> Unit,
|
||||
val onBack: () -> Unit,
|
||||
val onSkipClick: () -> Unit,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -77,11 +77,12 @@ private fun CurrencyConversionOptInPreview() =
|
|||
ZcashTheme {
|
||||
BlankSurface {
|
||||
ExchangeRateOptInView(
|
||||
state = ExchangeRateOptInState(
|
||||
onEnableClick = {},
|
||||
onBack = {},
|
||||
onSkipClick = {},
|
||||
)
|
||||
state =
|
||||
ExchangeRateOptInState(
|
||||
onEnableClick = {},
|
||||
onBack = {},
|
||||
onSkipClick = {},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,15 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
class ExchangeRateOptInViewModel(
|
||||
private val exchangeRateRepository: ExchangeRateRepository,
|
||||
private val navigationRouter: NavigationRouter
|
||||
): ViewModel() {
|
||||
val state: StateFlow<ExchangeRateOptInState> = MutableStateFlow(
|
||||
ExchangeRateOptInState(
|
||||
onBack = ::dismissOptInExchangeRateUsd,
|
||||
onEnableClick = ::optInExchangeRateUsd,
|
||||
onSkipClick = ::onSkipClick
|
||||
) : ViewModel() {
|
||||
val state: StateFlow<ExchangeRateOptInState> =
|
||||
MutableStateFlow(
|
||||
ExchangeRateOptInState(
|
||||
onBack = ::dismissOptInExchangeRateUsd,
|
||||
onEnableClick = ::optInExchangeRateUsd,
|
||||
onSkipClick = ::onSkipClick
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private fun onSkipClick() {
|
||||
exchangeRateRepository.optInExchangeRateUsd(false)
|
||||
|
@ -31,4 +32,4 @@ class ExchangeRateOptInViewModel(
|
|||
private fun dismissOptInExchangeRateUsd() {
|
||||
navigationRouter.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@ data class ExchangeRateSettingsState(
|
|||
val isOptedIn: Boolean,
|
||||
val onSaveClick: (optIn: Boolean) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -187,11 +187,12 @@ private fun SettingsExchangeRateOptInPreview() =
|
|||
ZcashTheme {
|
||||
BlankSurface {
|
||||
ExchangeRateSettingsView(
|
||||
state = ExchangeRateSettingsState(
|
||||
isOptedIn = true,
|
||||
onSaveClick = {},
|
||||
onDismiss = {}
|
||||
)
|
||||
state =
|
||||
ExchangeRateSettingsState(
|
||||
isOptedIn = true,
|
||||
onSaveClick = {},
|
||||
onDismiss = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,15 @@ class ExchangeRateSettingsViewModel(
|
|||
private val exchangeRateRepository: ExchangeRateRepository,
|
||||
private val navigationRouter: NavigationRouter,
|
||||
) : ViewModel() {
|
||||
|
||||
val state = exchangeRateRepository.state
|
||||
.map {
|
||||
createState(it)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = createState(exchangeRateRepository.state.value)
|
||||
)
|
||||
val state =
|
||||
exchangeRateRepository.state
|
||||
.map {
|
||||
createState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = createState(exchangeRateRepository.state.value)
|
||||
)
|
||||
|
||||
private fun createState(it: ExchangeRateState) =
|
||||
ExchangeRateSettingsState(
|
||||
|
@ -41,4 +40,4 @@ class ExchangeRateSettingsViewModel(
|
|||
exchangeRateRepository.optInExchangeRateUsd(optIn = optInt)
|
||||
navigationRouter.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,15 +18,16 @@ import org.koin.core.parameter.parametersOf
|
|||
@Composable
|
||||
internal fun AndroidHome() {
|
||||
val topAppBarViewModel = koinActivityViewModel<ZashiTopAppBarViewModel>()
|
||||
val balanceWidgetViewModel = koinViewModel<BalanceWidgetViewModel> {
|
||||
parametersOf(
|
||||
BalanceWidgetArgs(
|
||||
isBalanceButtonEnabled = false,
|
||||
isExchangeRateButtonEnabled = true,
|
||||
showDust = false,
|
||||
val balanceWidgetViewModel =
|
||||
koinViewModel<BalanceWidgetViewModel> {
|
||||
parametersOf(
|
||||
BalanceWidgetArgs(
|
||||
isBalanceButtonEnabled = false,
|
||||
isExchangeRateButtonEnabled = true,
|
||||
showDust = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val homeViewModel = koinViewModel<HomeViewModel>()
|
||||
val transactionHistoryWidgetViewModel = koinViewModel<TransactionHistoryWidgetViewModel>()
|
||||
val restoreDialogState by homeViewModel.restoreDialogState.collectAsStateWithLifecycle()
|
||||
|
|
|
@ -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.restoring.WalletRestoringMessage
|
||||
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState
|
||||
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessage
|
||||
import co.electriccoin.zcash.ui.screen.home.syncing.WalletSyncingMessageState
|
||||
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsMessage
|
||||
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.WalletUpdatingMessageState
|
||||
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> {
|
||||
val delayMs = delay?.inWholeMilliseconds?.toInt() ?: 0
|
||||
return tween(
|
||||
|
|
|
@ -107,8 +107,7 @@ private fun Container(
|
|||
0f to ZashiLightColors.Utility.Purple.utilityPurple500,
|
||||
1f to ZashiLightColors.Utility.Purple.utilityPurple900,
|
||||
)
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
).clickable(onClick = onClick)
|
||||
.padding(contentPadding),
|
||||
) {
|
||||
Row(
|
||||
|
|
|
@ -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.fixture.BalanceStateFixture
|
||||
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.BalanceWidgetState
|
||||
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.TransactionHistoryWidgetStateFixture
|
||||
|
|
|
@ -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.WalletDisconnectedMessageState
|
||||
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.CrashReportOptIn
|
||||
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringInfo
|
||||
import co.electriccoin.zcash.ui.screen.home.restoring.WalletRestoringMessageState
|
||||
import co.electriccoin.zcash.ui.screen.home.shieldfunds.ShieldFundsInfo
|
||||
|
@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class HomeViewModel(
|
||||
getHomeMessage: GetHomeMessageUseCase,
|
||||
getVersionInfoProvider: GetVersionInfoProvider,
|
||||
|
@ -63,15 +64,15 @@ class HomeViewModel(
|
|||
private val shieldFunds: ShieldFundsUseCase,
|
||||
private val navigateToError: NavigateToErrorUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
private val messageState = getHomeMessage
|
||||
.observe()
|
||||
.map { createMessageState(it) }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
private val messageState =
|
||||
getHomeMessage
|
||||
.observe()
|
||||
.map { createMessageState(it) }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
private val isRestoreDialogVisible: Flow<Boolean?> =
|
||||
isRestoreSuccessDialogVisible
|
||||
|
@ -155,54 +156,65 @@ class HomeViewModel(
|
|||
message = messageState
|
||||
)
|
||||
|
||||
private fun createMessageState(it: HomeMessageData?) = when (it) {
|
||||
is HomeMessageData.Backup -> WalletBackupMessageState(
|
||||
onClick = ::onWalletBackupMessageClick,
|
||||
onButtonClick = ::onWalletBackupMessageButtonClick,
|
||||
)
|
||||
private fun createMessageState(it: HomeMessageData?) =
|
||||
when (it) {
|
||||
is HomeMessageData.Backup ->
|
||||
WalletBackupMessageState(
|
||||
onClick = ::onWalletBackupMessageClick,
|
||||
onButtonClick = ::onWalletBackupMessageButtonClick,
|
||||
)
|
||||
|
||||
HomeMessageData.Disconnected -> WalletDisconnectedMessageState(
|
||||
onClick = ::onWalletDisconnectedMessageClick
|
||||
)
|
||||
HomeMessageData.Disconnected ->
|
||||
WalletDisconnectedMessageState(
|
||||
onClick = ::onWalletDisconnectedMessageClick
|
||||
)
|
||||
|
||||
HomeMessageData.EnableCurrencyConversion -> EnableCurrencyConversionMessageState(
|
||||
onClick = ::onEnableCurrencyConversionClick,
|
||||
onButtonClick = ::onEnableCurrencyConversionClick
|
||||
)
|
||||
HomeMessageData.EnableCurrencyConversion ->
|
||||
EnableCurrencyConversionMessageState(
|
||||
onClick = ::onEnableCurrencyConversionClick,
|
||||
onButtonClick = ::onEnableCurrencyConversionClick
|
||||
)
|
||||
|
||||
is HomeMessageData.Error -> WalletErrorMessageState(
|
||||
onClick = { onWalletErrorMessageClick(it) }
|
||||
)
|
||||
is HomeMessageData.Error ->
|
||||
WalletErrorMessageState(
|
||||
onClick = { onWalletErrorMessageClick(it) }
|
||||
)
|
||||
|
||||
is HomeMessageData.Restoring -> WalletRestoringMessageState(
|
||||
progress = it.progress,
|
||||
onClick = ::onWalletRestoringMessageClick
|
||||
)
|
||||
is HomeMessageData.Restoring ->
|
||||
WalletRestoringMessageState(
|
||||
progress = it.progress,
|
||||
onClick = ::onWalletRestoringMessageClick
|
||||
)
|
||||
|
||||
is HomeMessageData.Syncing -> WalletSyncingMessageState(
|
||||
progress = it.progress,
|
||||
onClick = ::onWalletSyncingMessageClick
|
||||
)
|
||||
is HomeMessageData.Syncing ->
|
||||
WalletSyncingMessageState(
|
||||
progress = it.progress,
|
||||
onClick = ::onWalletSyncingMessageClick
|
||||
)
|
||||
|
||||
is HomeMessageData.ShieldFunds -> ShieldFundsMessageState(
|
||||
subtitle = stringRes(
|
||||
R.string.home_message_transparent_balance_subtitle,
|
||||
stringRes(it.zatoshi)
|
||||
),
|
||||
onClick = ::onShieldFundsMessageClick,
|
||||
onButtonClick = ::onShieldFundsMessageButtonClick,
|
||||
)
|
||||
is HomeMessageData.ShieldFunds ->
|
||||
ShieldFundsMessageState(
|
||||
subtitle =
|
||||
stringRes(
|
||||
R.string.home_message_transparent_balance_subtitle,
|
||||
stringRes(it.zatoshi)
|
||||
),
|
||||
onClick = ::onShieldFundsMessageClick,
|
||||
onButtonClick = ::onShieldFundsMessageButtonClick,
|
||||
)
|
||||
|
||||
HomeMessageData.Updating -> WalletUpdatingMessageState(
|
||||
onClick = ::onWalletUpdatingMessageClick
|
||||
)
|
||||
HomeMessageData.Updating ->
|
||||
WalletUpdatingMessageState(
|
||||
onClick = ::onWalletUpdatingMessageClick
|
||||
)
|
||||
|
||||
HomeMessageData.CrashReport -> CrashReportMessageState(
|
||||
onClick = ::onCrashReportMessageClick,
|
||||
onButtonClick = ::onCrashReportMessageClick
|
||||
)
|
||||
null -> null
|
||||
}
|
||||
HomeMessageData.CrashReport ->
|
||||
CrashReportMessageState(
|
||||
onClick = ::onCrashReportMessageClick,
|
||||
onButtonClick = ::onCrashReportMessageClick
|
||||
)
|
||||
null -> null
|
||||
}
|
||||
|
||||
private fun onCrashReportMessageClick() = navigationRouter.forward(CrashReportOptIn)
|
||||
|
||||
|
|
|
@ -20,10 +20,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.ButtonState
|
||||
import co.electriccoin.zcash.ui.design.component.HorizontalSpacer
|
||||
import co.electriccoin.zcash.ui.design.component.IconButtonState
|
||||
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.ZashiIconButton
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiInfoRow
|
||||
|
|
|
@ -14,13 +14,14 @@ class WalletBackupDetailViewModel(
|
|||
private val navigationRouter: NavigationRouter,
|
||||
private val navigateToWalletBackup: NavigateToWalletBackupUseCase
|
||||
) : ViewModel() {
|
||||
val state = MutableStateFlow(
|
||||
WalletBackupDetailState(
|
||||
onBack = ::onBack,
|
||||
onNextClick = ::onNextClick,
|
||||
onInfoClick = ::onInfoClick
|
||||
)
|
||||
).asStateFlow()
|
||||
val state =
|
||||
MutableStateFlow(
|
||||
WalletBackupDetailState(
|
||||
onBack = ::onBack,
|
||||
onNextClick = ::onNextClick,
|
||||
onInfoClick = ::onInfoClick
|
||||
)
|
||||
).asStateFlow()
|
||||
|
||||
private fun onNextClick() =
|
||||
viewModelScope.launch {
|
||||
|
@ -39,5 +40,3 @@ class WalletBackupDetailViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,11 +42,11 @@ fun WalletBackupInfoView(
|
|||
state = state
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f, false)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp)
|
||||
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f, false)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_info_backup),
|
||||
|
@ -118,20 +118,23 @@ private fun Preview() =
|
|||
WalletBackupInfoView(
|
||||
WalletBackupInfoState(
|
||||
onBack = {},
|
||||
secondaryButton = ButtonState(
|
||||
text = stringRes(R.string.general_remind_me_later),
|
||||
onClick = {},
|
||||
isEnabled = false
|
||||
),
|
||||
primaryButton = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = {}
|
||||
),
|
||||
checkboxState = CheckboxState(
|
||||
isChecked = false,
|
||||
onClick = {},
|
||||
text = stringRes(R.string.home_info_backup_checkbox)
|
||||
)
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_remind_me_later),
|
||||
onClick = {},
|
||||
isEnabled = false
|
||||
),
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = {}
|
||||
),
|
||||
checkboxState =
|
||||
CheckboxState(
|
||||
isChecked = false,
|
||||
onClick = {},
|
||||
text = stringRes(R.string.home_info_backup_checkbox)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,44 +31,47 @@ class WalletBackupInfoViewModel(
|
|||
private val navigationRouter: NavigationRouter,
|
||||
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val isConsentChecked = MutableStateFlow(false)
|
||||
|
||||
private val lockoutDuration = walletBackupDataSource
|
||||
.observe()
|
||||
.filterIsInstance<WalletBackupAvailability.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
private val lockoutDuration =
|
||||
walletBackupDataSource
|
||||
.observe()
|
||||
.filterIsInstance<WalletBackupAvailability.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
val state: StateFlow<WalletBackupInfoState?> = combine(
|
||||
lockoutDuration.filterNotNull(),
|
||||
isConsentChecked,
|
||||
walletBackupConsentStorageProvider.observe().take(1)
|
||||
) { lockout, isConsentChecked, isConsentSaved ->
|
||||
val state: StateFlow<WalletBackupInfoState?> =
|
||||
combine(
|
||||
lockoutDuration.filterNotNull(),
|
||||
isConsentChecked,
|
||||
walletBackupConsentStorageProvider.observe().take(1)
|
||||
) { lockout, isConsentChecked, isConsentSaved ->
|
||||
WalletBackupInfoState(
|
||||
onBack = ::onBack,
|
||||
secondaryButton = ButtonState(
|
||||
text = stringRes(R.string.general_remind_me_in, stringRes(lockout.res)),
|
||||
onClick = ::onRemindMeLaterClick,
|
||||
isEnabled = isConsentChecked || isConsentSaved
|
||||
),
|
||||
checkboxState = CheckboxState(
|
||||
isChecked = isConsentChecked,
|
||||
onClick = ::onConsentClick,
|
||||
text = stringRes(R.string.home_info_backup_checkbox)
|
||||
).takeIf { !isConsentSaved },
|
||||
primaryButton = ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = ::onPrimaryClick
|
||||
)
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_remind_me_in, stringRes(lockout.res)),
|
||||
onClick = ::onRemindMeLaterClick,
|
||||
isEnabled = isConsentChecked || isConsentSaved
|
||||
),
|
||||
checkboxState =
|
||||
CheckboxState(
|
||||
isChecked = isConsentChecked,
|
||||
onClick = ::onConsentClick,
|
||||
text = stringRes(R.string.home_info_backup_checkbox)
|
||||
).takeIf { !isConsentSaved },
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
text = stringRes(R.string.general_ok),
|
||||
onClick = ::onPrimaryClick
|
||||
)
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
|
|
|
@ -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
|
||||
@Composable
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
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.ZashiButtonDefaults
|
||||
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.colors.ZashiColors
|
||||
import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography
|
||||
import co.electriccoin.zcash.ui.design.component.ZashiBaseSettingsOptIn
|
||||
|
||||
@Composable
|
||||
fun CrashReportOptInView(state: CrashReportOptInState) {
|
||||
|
@ -76,11 +76,12 @@ private fun Preview() =
|
|||
ZcashTheme {
|
||||
BlankSurface {
|
||||
CrashReportOptInView(
|
||||
state = CrashReportOptInState(
|
||||
onOptInClick = {},
|
||||
onBack = {},
|
||||
onOptOutClick = {},
|
||||
)
|
||||
state =
|
||||
CrashReportOptInState(
|
||||
onOptInClick = {},
|
||||
onBack = {},
|
||||
onOptOutClick = {},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,15 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
class CrashReportOptInViewModel(
|
||||
private val exchangeRateRepository: ExchangeRateRepository,
|
||||
private val navigationRouter: NavigationRouter
|
||||
): ViewModel() {
|
||||
val state: StateFlow<CrashReportOptInState> = MutableStateFlow(
|
||||
CrashReportOptInState(
|
||||
onBack = ::dismissOptInExchangeRateUsd,
|
||||
onOptInClick = ::optInExchangeRateUsd,
|
||||
onOptOutClick = ::onSkipClick
|
||||
) : ViewModel() {
|
||||
val state: StateFlow<CrashReportOptInState> =
|
||||
MutableStateFlow(
|
||||
CrashReportOptInState(
|
||||
onBack = ::dismissOptInExchangeRateUsd,
|
||||
onOptInClick = ::optInExchangeRateUsd,
|
||||
onOptOutClick = ::onSkipClick
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private fun onSkipClick() {
|
||||
exchangeRateRepository.optInExchangeRateUsd(false)
|
||||
|
@ -31,4 +32,4 @@ class CrashReportOptInViewModel(
|
|||
private fun dismissOptInExchangeRateUsd() {
|
||||
navigationRouter.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
|||
import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.ui.NavigationRouter
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.datasource.WalletBackupAvailability
|
||||
import co.electriccoin.zcash.ui.common.repository.ShieldFundsData
|
||||
import co.electriccoin.zcash.ui.common.repository.ShieldFundsRepository
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetSelectedWalletAccountUseCase
|
||||
|
@ -32,43 +31,42 @@ class ShieldFundsInfoViewModel(
|
|||
private val remindShieldFundsLater: RemindShieldFundsLaterUseCase,
|
||||
private val shieldFunds: ShieldFundsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val lockoutDuration = shieldFundsRepository
|
||||
.availability
|
||||
.filterIsInstance<ShieldFundsData.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
private val lockoutDuration =
|
||||
shieldFundsRepository
|
||||
.availability
|
||||
.filterIsInstance<ShieldFundsData.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
val state: StateFlow<ShieldFundsInfoState?> =
|
||||
combine(
|
||||
getSelectedWalletAccount.observe(),
|
||||
lockoutDuration.filterNotNull(),
|
||||
) { account, lockoutDuration ->
|
||||
ShieldFundsInfoState(
|
||||
onBack = ::onBack,
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
onClick = ::onShieldClick,
|
||||
text = stringRes(R.string.home_info_transparent_balance_shield)
|
||||
),
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
onClick = ::onRemindMeClick,
|
||||
text = stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
|
||||
),
|
||||
transparentAmount = account?.transparent?.balance ?: Zatoshi(0)
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
ShieldFundsInfoState(
|
||||
onBack = ::onBack,
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
onClick = ::onShieldClick,
|
||||
text = stringRes(R.string.home_info_transparent_balance_shield)
|
||||
),
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
onClick = ::onRemindMeClick,
|
||||
text = stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
|
||||
),
|
||||
transparentAmount = account?.transparent?.balance ?: Zatoshi(0)
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
private fun onRemindMeClick() = viewModelScope.launch { remindShieldFundsLater() }
|
||||
|
||||
|
@ -76,4 +74,3 @@ class ShieldFundsInfoViewModel(
|
|||
|
||||
private fun onShieldClick() = shieldFunds(closeCurrentScreen = true)
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,6 @@ fun MainActivity.OnboardingNavigation() {
|
|||
val flexaViewModel = koinViewModel<FlexaViewModel>()
|
||||
val messageAvailabilityDataSource = koinInject<MessageAvailabilityDataSource>()
|
||||
|
||||
|
||||
val navigator: Navigator =
|
||||
remember(
|
||||
navController,
|
||||
|
|
|
@ -17,4 +17,6 @@ fun AndroidRestoreBDDate(args: RestoreBDDate) {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class RestoreBDDate(val seed: String)
|
||||
data class RestoreBDDate(
|
||||
val seed: String
|
||||
)
|
||||
|
|
|
@ -31,18 +31,18 @@ class RestoreBDDateViewModel(
|
|||
private val navigationRouter: NavigationRouter,
|
||||
private val context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private val selection = MutableStateFlow<YearMonth>(YearMonth.of(2018, 10))
|
||||
|
||||
val state: StateFlow<RestoreBDDateState?> = selection
|
||||
.map {
|
||||
createState(it)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
val state: StateFlow<RestoreBDDateState?> =
|
||||
selection
|
||||
.map {
|
||||
createState(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
private fun createState(selection: YearMonth) =
|
||||
RestoreBDDateState(
|
||||
|
@ -59,16 +59,19 @@ class RestoreBDDateViewModel(
|
|||
|
||||
private fun onEstimateClick() {
|
||||
viewModelScope.launch {
|
||||
val instant = selection.value.atDay(1)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toKotlinInstant()
|
||||
val bday = SdkSynchronizer.estimateBirthdayHeight(
|
||||
context = context,
|
||||
date = instant,
|
||||
network = ZcashNetwork.fromResources(context)
|
||||
)
|
||||
val instant =
|
||||
selection.value
|
||||
.atDay(1)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toKotlinInstant()
|
||||
val bday =
|
||||
SdkSynchronizer.estimateBirthdayHeight(
|
||||
context = context,
|
||||
date = instant,
|
||||
network = ZcashNetwork.fromResources(context)
|
||||
)
|
||||
navigationRouter.forward(RestoreBDEstimation(seed = args.seed, blockHeight = bday.value))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.fixture.BalanceStateFixture
|
||||
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.BalanceWidgetState
|
||||
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.MemoState
|
||||
|
@ -331,7 +331,7 @@ private fun SendForm(
|
|||
recipientAddressState.type is AddressType.Valid &&
|
||||
recipientAddressState.type !is AddressType.Transparent &&
|
||||
recipientAddressState.type !is AddressType.Tex
|
||||
)
|
||||
)
|
||||
|
||||
SendFormAmountTextField(
|
||||
amountState = amountState,
|
||||
|
@ -653,9 +653,10 @@ fun SendFormAmountTextField(
|
|||
)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
innerModifier = ZashiTextFieldDefaults
|
||||
.innerModifier
|
||||
.testTag(SendTag.SEND_AMOUNT_FIELD),
|
||||
innerModifier =
|
||||
ZashiTextFieldDefaults
|
||||
.innerModifier
|
||||
.testTag(SendTag.SEND_AMOUNT_FIELD),
|
||||
error = amountError,
|
||||
placeholder = {
|
||||
Text(
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
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.core.parameter.parametersOf
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.di.koinActivityViewModel
|
||||
import co.electriccoin.zcash.ui.common.appbar.ZashiTopAppBarViewModel
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -17,4 +17,6 @@ internal fun AndroidWalletBackup(args: WalletBackup) {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class WalletBackup(val isOpenedFromSeedBackupInfo: Boolean)
|
||||
data class WalletBackup(
|
||||
val isOpenedFromSeedBackupInfo: Boolean
|
||||
)
|
||||
|
|
|
@ -291,11 +291,12 @@ private fun RevealedPreview() =
|
|||
isRevealed = true,
|
||||
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
|
||||
) {},
|
||||
secondaryButton = ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
|
@ -331,11 +332,12 @@ private fun HiddenPreview() =
|
|||
isRevealed = false,
|
||||
tooltip = SeedSecretStateTooltip(title = stringRes(""), message = stringRes("")),
|
||||
) {},
|
||||
secondaryButton = ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
icon = R.drawable.ic_seed_show,
|
||||
onClick = {},
|
||||
),
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
text = stringRes("Text"),
|
||||
|
|
|
@ -34,24 +34,25 @@ class WalletBackupViewModel(
|
|||
private val onUserSavedWalletBackup: OnUserSavedWalletBackupUseCase,
|
||||
private val remindWalletBackupLater: RemindWalletBackupLaterUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val lockoutDuration = walletBackupDataSource
|
||||
.observe()
|
||||
.filterIsInstance<WalletBackupAvailability.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
private val lockoutDuration =
|
||||
walletBackupDataSource
|
||||
.observe()
|
||||
.filterIsInstance<WalletBackupAvailability.Available>()
|
||||
.take(1)
|
||||
.map { it.lockoutDuration }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
private val isRevealed = MutableStateFlow(false)
|
||||
|
||||
private val isRemindMeLaterButtonVisible = isRevealed
|
||||
.map { isRevealed ->
|
||||
isRevealed && args.isOpenedFromSeedBackupInfo
|
||||
}
|
||||
private val isRemindMeLaterButtonVisible =
|
||||
isRevealed
|
||||
.map { isRevealed ->
|
||||
isRevealed && args.isOpenedFromSeedBackupInfo
|
||||
}
|
||||
|
||||
private val observableWallet = observePersistableWallet()
|
||||
|
||||
|
@ -63,14 +64,16 @@ class WalletBackupViewModel(
|
|||
lockoutDuration
|
||||
) { isRevealed, isRemindMeLaterButtonVisible, wallet, lockoutDuration ->
|
||||
WalletBackupState(
|
||||
secondaryButton = ButtonState(
|
||||
text = if (lockoutDuration != null) {
|
||||
stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
|
||||
} else {
|
||||
stringRes(R.string.general_remind_me_later)
|
||||
},
|
||||
onClick = ::onRemindMeLaterClick
|
||||
).takeIf { isRemindMeLaterButtonVisible },
|
||||
secondaryButton =
|
||||
ButtonState(
|
||||
text =
|
||||
if (lockoutDuration != null) {
|
||||
stringRes(R.string.general_remind_me_in, stringRes(lockoutDuration.res))
|
||||
} else {
|
||||
stringRes(R.string.general_remind_me_later)
|
||||
},
|
||||
onClick = ::onRemindMeLaterClick
|
||||
).takeIf { isRemindMeLaterButtonVisible },
|
||||
primaryButton =
|
||||
ButtonState(
|
||||
text =
|
||||
|
@ -80,11 +83,12 @@ class WalletBackupViewModel(
|
|||
isRevealed -> stringRes(R.string.seed_recovery_hide_button)
|
||||
else -> stringRes(R.string.seed_recovery_reveal_button)
|
||||
},
|
||||
onClick = if (isRevealed && args.isOpenedFromSeedBackupInfo) {
|
||||
{ onWalletBackupSavedClick() }
|
||||
} else {
|
||||
{ onRevealClick() }
|
||||
},
|
||||
onClick =
|
||||
if (isRevealed && args.isOpenedFromSeedBackupInfo) {
|
||||
{ onWalletBackupSavedClick() }
|
||||
} else {
|
||||
{ onRevealClick() }
|
||||
},
|
||||
isEnabled = wallet != null,
|
||||
isLoading = wallet == null,
|
||||
icon =
|
||||
|
@ -144,4 +148,3 @@ class WalletBackupViewModel(
|
|||
|
||||
private fun onBack() = navigationRouter.back()
|
||||
}
|
||||
|
||||
|
|
|
@ -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.restore.height.RestoreBDHeightTags
|
||||
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.walletbackup.WalletBackup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
Loading…
Reference in New Issue