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