[#1449] Display Synchronizer details in dialog
- Closes #1449 - Synchronizer status details are now available to users by pressing the simple status view. The details are displayed within the predefined Zashi dialog on the Balances and Account screens - As this view also presents information about Zashi app updates available on Google Play, by pressing the view, the app redirects users to Google Play Zashi’s page - As agreed, we’re moving towards more rounded corners in dialogs, which is part of these changes, too - Added also several minor Balances screen UI improvements - Improved biometric flow without any authentication method set on older Android versions - Changelog update
This commit is contained in:
parent
00db536674
commit
7e9e89725b
|
@ -14,6 +14,14 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
cases: Send funds, Recovery Phrase, Export Private Data, and Delete Wallet.
|
||||
- The app entry animation has been reworked to apply on every app access point, i.e. it will be displayed when
|
||||
users return to an already set up app as well.
|
||||
- Synchronizer status details are now available to users by pressing the simple status view placed above the
|
||||
synchronization progress bar. The details are displayed within a dialog window on the Balances and Account screens.
|
||||
This view also occasionally presents information about a possible Zashi app update available on Google Play. The
|
||||
app redirects users to the Google Play Zashi page by pressing the view.
|
||||
|
||||
### Changed
|
||||
- The app dialog window has now a bit more rounded corners
|
||||
- A few more minor UI improvements
|
||||
|
||||
## [1.0 (650)] - 2024-05-07
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -61,7 +62,7 @@ fun AppAlertDialog(
|
|||
properties: DialogProperties = DialogProperties()
|
||||
) {
|
||||
AlertDialog(
|
||||
shape = RectangleShape,
|
||||
shape = RoundedCornerShape(corner = CornerSize(ZcashTheme.dimens.regularRippleEffectCorner)),
|
||||
onDismissRequest = onDismissRequest?.let { onDismissRequest } ?: {},
|
||||
confirmButton = {
|
||||
confirmButtonText?.let {
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp
|
|||
data class Dimens(
|
||||
// Default spacings:
|
||||
val spacingNone: Dp,
|
||||
val spacingMini: Dp,
|
||||
val spacingXtiny: Dp,
|
||||
val spacingMin: Dp,
|
||||
val spacingTiny: Dp,
|
||||
|
@ -61,6 +62,7 @@ data class Dimens(
|
|||
private val defaultDimens =
|
||||
Dimens(
|
||||
spacingNone = 0.dp,
|
||||
spacingMini = 1.dp,
|
||||
spacingXtiny = 2.dp,
|
||||
spacingTiny = 4.dp,
|
||||
spacingMin = 6.dp,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.screen.account
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
|
@ -70,6 +71,10 @@ class AccountTestSetup(
|
|||
onTransactionItemAction = {
|
||||
onItemClickCount.incrementAndGet()
|
||||
},
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
onStatusClick = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
walletRestoringState = WalletRestoringState.NONE,
|
||||
walletSnapshot = WalletSnapshotFixture.new()
|
||||
)
|
||||
|
|
|
@ -30,6 +30,7 @@ class HistoryTestSetup(
|
|||
ZcashTheme {
|
||||
HistoryContainer(
|
||||
transactionState = initialHistoryUiState,
|
||||
onStatusClick = {},
|
||||
onTransactionItemAction = {
|
||||
onItemIdClickCount.incrementAndGet()
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.screen.balances
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
|
@ -35,7 +36,10 @@ class BalancesTestSetup(
|
|||
onSettings = {
|
||||
onSettingsCount.incrementAndGet()
|
||||
},
|
||||
isDetailedStatus = false,
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
onStatusClick = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
isFiatConversionEnabled = isShowFiatConversion,
|
||||
isUpdateAvailable = false,
|
||||
isShowingErrorDialog = false,
|
||||
|
|
|
@ -31,8 +31,7 @@ class WalletDisplayValuesTest {
|
|||
WalletDisplayValues.getNextValues(
|
||||
context = getAppContext(),
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = false,
|
||||
isDetailedStatus = false
|
||||
isUpdateAvailable = false
|
||||
)
|
||||
|
||||
assertNotNull(values)
|
||||
|
|
|
@ -3,6 +3,7 @@ package co.electriccoin.zcash.ui.screen.update.util
|
|||
import android.content.Intent
|
||||
import androidx.test.filters.SmallTest
|
||||
import co.electriccoin.zcash.ui.test.getAppContext
|
||||
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
|
|
|
@ -80,6 +80,7 @@ sealed class BalanceState(open val totalBalance: Zatoshi) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
fun BalanceWidget(
|
||||
balanceState: BalanceState,
|
||||
isReferenceToBalances: Boolean,
|
||||
|
@ -104,11 +105,22 @@ fun BalanceWidget(
|
|||
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
||||
onClick = onReferenceClick,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
vertical = ZcashTheme.dimens.spacingSmall,
|
||||
horizontal = ZcashTheme.dimens.spacingMini,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Body(
|
||||
text = stringResource(id = co.electriccoin.zcash.ui.R.string.balance_widget_available),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
vertical = ZcashTheme.dimens.spacingSmall,
|
||||
horizontal = ZcashTheme.dimens.spacingMini,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package co.electriccoin.zcash.ui.common.compose
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -18,11 +24,13 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import cash.z.ecc.sdk.extension.toPercentageWithDecimal
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.ui.design.component.AppAlertDialog
|
||||
import co.electriccoin.zcash.ui.design.component.BodySmall
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.SmallLinearProgressIndicator
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
||||
|
||||
@Preview(device = Devices.PIXEL_4_XL)
|
||||
|
@ -34,8 +42,8 @@ private fun BalanceWidgetPreview() {
|
|||
) {
|
||||
SynchronizationStatus(
|
||||
isUpdateAvailable = false,
|
||||
isDetailedStatus = false,
|
||||
walletSnapshot = WalletSnapshotFixture.new()
|
||||
onStatusClick = {},
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +52,7 @@ private fun BalanceWidgetPreview() {
|
|||
@Composable
|
||||
fun SynchronizationStatus(
|
||||
isUpdateAvailable: Boolean,
|
||||
isDetailedStatus: Boolean,
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
modifier: Modifier = Modifier,
|
||||
testTag: String? = null,
|
||||
|
@ -54,7 +62,6 @@ fun SynchronizationStatus(
|
|||
context = LocalContext.current,
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = isUpdateAvailable,
|
||||
isDetailedStatus = isDetailedStatus
|
||||
)
|
||||
|
||||
Column(
|
||||
|
@ -64,11 +71,14 @@ fun SynchronizationStatus(
|
|||
if (walletDisplayValues.statusText.isNotEmpty()) {
|
||||
BodySmall(
|
||||
text = walletDisplayValues.statusText,
|
||||
modifier = testTag?.let { Modifier.testTag(testTag) } ?: Modifier,
|
||||
textAlign = TextAlign.Center
|
||||
textAlign = TextAlign.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable { onStatusClick(walletDisplayValues.statusAction) }
|
||||
.padding(all = ZcashTheme.dimens.spacingSmall)
|
||||
.then(testTag?.let { Modifier.testTag(testTag) } ?: Modifier),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
}
|
||||
|
||||
BodySmall(
|
||||
|
@ -86,8 +96,27 @@ fun SynchronizationStatus(
|
|||
progress = walletSnapshot.progress.decimal,
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
horizontal = ZcashTheme.dimens.spacingUpLarge
|
||||
horizontal = ZcashTheme.dimens.spacingDefault
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatusDialog(
|
||||
statusAction: StatusAction.Detailed,
|
||||
onDone: () -> Unit
|
||||
) {
|
||||
AppAlertDialog(
|
||||
title = stringResource(id = R.string.balances_status_error_dialog_title),
|
||||
text = {
|
||||
Column(
|
||||
Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(text = statusAction.details)
|
||||
}
|
||||
},
|
||||
confirmButtonText = stringResource(id = R.string.balances_status_dialog_button),
|
||||
onConfirmButtonClick = onDone
|
||||
)
|
||||
}
|
||||
|
|
|
@ -185,8 +185,6 @@ class AuthenticationViewModel(
|
|||
// Biometric authentication is disabled until the user unlocks with their device credential
|
||||
// (i.e. PIN, pattern, or password).
|
||||
BiometricPrompt.ERROR_LOCKOUT_PERMANENT,
|
||||
// The user does not have any biometrics enrolled
|
||||
BiometricPrompt.ERROR_NO_BIOMETRICS,
|
||||
// The device does not have the required authentication hardware
|
||||
BiometricPrompt.ERROR_HW_NOT_PRESENT,
|
||||
// The user pressed the negative button
|
||||
|
@ -214,9 +212,12 @@ class AuthenticationViewModel(
|
|||
// We could consider splitting ERROR_CANCELED from ERROR_USER_CANCELED
|
||||
authenticationResult.value = AuthenticationResult.Canceled
|
||||
}
|
||||
// The user does not have any biometrics enrolled
|
||||
BiometricPrompt.ERROR_NO_BIOMETRICS,
|
||||
// The device does not have pin, pattern, or password set up
|
||||
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||
// Allow unauthenticated access if no authentication method is available on the device
|
||||
// These 2 errors can come for a different Android SDK versions, but they mean the same
|
||||
authenticationResult.value = AuthenticationResult.Success
|
||||
}
|
||||
}
|
||||
|
@ -346,7 +347,7 @@ class AuthenticationViewModel(
|
|||
}
|
||||
else -> {
|
||||
Twig.error { "Unexpected biometric framework status" }
|
||||
BiometricSupportResult.StatusExpected
|
||||
BiometricSupportResult.StatusUnexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,5 +412,5 @@ private sealed class BiometricSupportResult {
|
|||
|
||||
data object StatusUnknown : BiometricSupportResult()
|
||||
|
||||
data object StatusExpected : BiometricSupportResult()
|
||||
data object StatusUnexpected : BiometricSupportResult()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ package co.electriccoin.zcash.ui.screen.account
|
|||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
|
@ -21,6 +24,8 @@ import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
|||
import co.electriccoin.zcash.ui.screen.account.view.Account
|
||||
import co.electriccoin.zcash.ui.screen.account.view.TrxItemAction
|
||||
import co.electriccoin.zcash.ui.screen.account.viewmodel.TransactionHistoryViewModel
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
@ -31,8 +36,6 @@ internal fun WrapAccount(
|
|||
goBalances: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val transactionHistoryViewModel by activity.viewModels<TransactionHistoryViewModel>()
|
||||
|
@ -56,7 +59,6 @@ internal fun WrapAccount(
|
|||
context = activity.applicationContext,
|
||||
goBalances = goBalances,
|
||||
goSettings = goSettings,
|
||||
scope = scope,
|
||||
synchronizer = synchronizer,
|
||||
transactionHistoryViewModel = transactionHistoryViewModel,
|
||||
transactionsUiState = transactionsUiState,
|
||||
|
@ -70,11 +72,10 @@ internal fun WrapAccount(
|
|||
|
||||
@Composable
|
||||
@VisibleForTesting
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
internal fun WrapAccount(
|
||||
balanceState: BalanceState,
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
goBalances: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
transactionsUiState: TransactionUiState,
|
||||
|
@ -83,6 +84,14 @@ internal fun WrapAccount(
|
|||
walletRestoringState: WalletRestoringState,
|
||||
walletSnapshot: WalletSnapshot?
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
// We could also improve this by `rememberSaveable` to preserve the dialog after a configuration change. But the
|
||||
// dialog dismissing in such cases is not critical, and it would require creating StatusAction custom Saver
|
||||
val showStatusDialog = remember { mutableStateOf<StatusAction.Detailed?>(null) }
|
||||
|
||||
if (null == synchronizer || null == walletSnapshot) {
|
||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||
|
@ -92,6 +101,23 @@ internal fun WrapAccount(
|
|||
Account(
|
||||
balanceState = balanceState,
|
||||
transactionsUiState = transactionsUiState,
|
||||
showStatusDialog = showStatusDialog.value,
|
||||
hideStatusDialog = { showStatusDialog.value = null },
|
||||
onStatusClick = { status ->
|
||||
when (status) {
|
||||
is StatusAction.Detailed -> showStatusDialog.value = status
|
||||
StatusAction.AppUpdate -> {
|
||||
openPlayStoreAppSite(
|
||||
context = context,
|
||||
snackbarHostState = snackbarHostState,
|
||||
scope = scope
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// No action required
|
||||
}
|
||||
}
|
||||
},
|
||||
onTransactionItemAction = { action ->
|
||||
when (action) {
|
||||
is TrxItemAction.TransactionIdClick -> {
|
||||
|
@ -132,8 +158,26 @@ internal fun WrapAccount(
|
|||
},
|
||||
goBalances = goBalances,
|
||||
goSettings = goSettings,
|
||||
snackbarHostState = snackbarHostState,
|
||||
walletRestoringState = walletRestoringState,
|
||||
walletSnapshot = walletSnapshot
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPlayStoreAppSite(
|
||||
context: Context,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
scope: CoroutineScope
|
||||
) {
|
||||
val storeIntent = PlayStoreUtil.newActivityIntent(context)
|
||||
runCatching {
|
||||
context.startActivity(storeIntent)
|
||||
}.onFailure {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = context.getString(R.string.unable_to_open_play_store)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -19,6 +21,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
|||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
||||
import co.electriccoin.zcash.ui.common.compose.StatusDialog
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||
|
@ -30,6 +33,7 @@ import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
|||
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
|
||||
@Preview("Account No History")
|
||||
@Composable
|
||||
|
@ -37,12 +41,16 @@ private fun HistoryLoadingComposablePreview() {
|
|||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Account(
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
goBalances = {},
|
||||
goSettings = {},
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
onStatusClick = {},
|
||||
onTransactionItemAction = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
transactionsUiState = TransactionUiState.Loading,
|
||||
walletRestoringState = WalletRestoringState.SYNCING,
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
)
|
||||
}
|
||||
|
@ -56,10 +64,14 @@ private fun HistoryListComposablePreview() {
|
|||
GradientSurface {
|
||||
@Suppress("MagicNumber")
|
||||
Account(
|
||||
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)),
|
||||
goBalances = {},
|
||||
goSettings = {},
|
||||
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)),
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
onStatusClick = {},
|
||||
onTransactionItemAction = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
transactionsUiState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
||||
walletRestoringState = WalletRestoringState.NONE,
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
|
@ -74,20 +86,30 @@ internal fun Account(
|
|||
balanceState: BalanceState,
|
||||
goBalances: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
hideStatusDialog: () -> Unit,
|
||||
showStatusDialog: StatusAction.Detailed?,
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
transactionsUiState: TransactionUiState,
|
||||
walletRestoringState: WalletRestoringState,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
) {
|
||||
Scaffold(topBar = {
|
||||
AccountTopAppBar(
|
||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||
onSettings = goSettings
|
||||
)
|
||||
}) { paddingValues ->
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AccountTopAppBar(
|
||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||
onSettings = goSettings
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackbarHostState)
|
||||
},
|
||||
) { paddingValues ->
|
||||
AccountMainContent(
|
||||
balanceState = balanceState,
|
||||
goBalances = goBalances,
|
||||
onStatusClick = onStatusClick,
|
||||
onTransactionItemAction = onTransactionItemAction,
|
||||
transactionState = transactionsUiState,
|
||||
walletRestoringState = walletRestoringState,
|
||||
|
@ -99,6 +121,14 @@ internal fun Account(
|
|||
// underlying transaction history composable
|
||||
)
|
||||
)
|
||||
|
||||
// Show synchronization status popup
|
||||
if (showStatusDialog != null) {
|
||||
StatusDialog(
|
||||
statusAction = showStatusDialog,
|
||||
onDone = hideStatusDialog
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,6 +165,7 @@ private fun AccountMainContent(
|
|||
balanceState: BalanceState,
|
||||
goBalances: () -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
transactionState: TransactionUiState,
|
||||
walletRestoringState: WalletRestoringState,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
|
@ -157,6 +188,7 @@ private fun AccountMainContent(
|
|||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||
|
||||
HistoryContainer(
|
||||
onStatusClick = onStatusClick,
|
||||
onTransactionItemAction = onTransactionItemAction,
|
||||
transactionState = transactionState,
|
||||
walletRestoringState = walletRestoringState,
|
||||
|
|
|
@ -62,6 +62,7 @@ import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
|||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
||||
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.screen.send.view.DEFAULT_LESS_THAN_FEE
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
@ -76,6 +77,7 @@ private fun ComposablePreview() {
|
|||
GradientSurface {
|
||||
HistoryContainer(
|
||||
onTransactionItemAction = {},
|
||||
onStatusClick = {},
|
||||
transactionState = TransactionUiState.Loading,
|
||||
walletRestoringState = WalletRestoringState.SYNCING,
|
||||
walletSnapshot = WalletSnapshotFixture.new()
|
||||
|
@ -92,6 +94,7 @@ private fun ComposableHistoryListPreview() {
|
|||
HistoryContainer(
|
||||
transactionState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
|
||||
onTransactionItemAction = {},
|
||||
onStatusClick = {},
|
||||
walletRestoringState = WalletRestoringState.RESTORING,
|
||||
walletSnapshot = WalletSnapshotFixture.new()
|
||||
)
|
||||
|
@ -107,7 +110,9 @@ private val dateFormat: DateFormat by lazy {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
internal fun HistoryContainer(
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
transactionState: TransactionUiState,
|
||||
walletRestoringState: WalletRestoringState,
|
||||
|
@ -127,15 +132,19 @@ internal fun HistoryContainer(
|
|||
Column(
|
||||
modifier = Modifier.background(color = ZcashTheme.colors.historySyncingColor)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
// Do not show the app update information and the detailed sync status in the restoring status
|
||||
// on Account screen
|
||||
// Do not calculate and use the app update information here, as the sync bar won't be displayed after
|
||||
// the wallet is fully restored
|
||||
SynchronizationStatus(
|
||||
isDetailedStatus = false,
|
||||
isUpdateAvailable = false,
|
||||
onStatusClick = onStatusClick,
|
||||
testTag = BalancesTag.STATUS,
|
||||
walletSnapshot = walletSnapshot,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
.animateContentSize()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
package co.electriccoin.zcash.ui.screen.balances
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -26,11 +29,14 @@ import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
|||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.screen.balances.view.Balances
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SubmitResult
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.viewmodel.CreateTransactionsViewModel
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
@ -38,7 +44,6 @@ import org.jetbrains.annotations.VisibleForTesting
|
|||
@Composable
|
||||
internal fun WrapBalances(
|
||||
activity: ComponentActivity,
|
||||
isDetailedSyncStatus: Boolean,
|
||||
goSettings: () -> Unit,
|
||||
goMultiTrxSubmissionFailure: () -> Unit,
|
||||
) {
|
||||
|
@ -69,7 +74,6 @@ internal fun WrapBalances(
|
|||
checkUpdateViewModel = checkUpdateViewModel,
|
||||
goSettings = goSettings,
|
||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
||||
spendingKey = spendingKey,
|
||||
synchronizer = synchronizer,
|
||||
walletSnapshot = walletSnapshot,
|
||||
|
@ -81,14 +85,14 @@ const val DEFAULT_SHIELDING_THRESHOLD = 100000L
|
|||
|
||||
@Composable
|
||||
@VisibleForTesting
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
// This function should be refactored into smaller chunks
|
||||
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")
|
||||
internal fun WrapBalances(
|
||||
balanceState: BalanceState,
|
||||
checkUpdateViewModel: CheckUpdateViewModel,
|
||||
createTransactionsViewModel: CreateTransactionsViewModel,
|
||||
goSettings: () -> Unit,
|
||||
goMultiTrxSubmissionFailure: () -> Unit,
|
||||
isDetailedSyncStatus: Boolean,
|
||||
spendingKey: UnifiedSpendingKey?,
|
||||
synchronizer: Synchronizer?,
|
||||
walletSnapshot: WalletSnapshot?,
|
||||
|
@ -98,6 +102,8 @@ internal fun WrapBalances(
|
|||
|
||||
val context = LocalContext.current
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
// To show information about the app update, if available
|
||||
val isUpdateAvailable =
|
||||
checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value.let {
|
||||
|
@ -130,6 +136,10 @@ internal fun WrapBalances(
|
|||
setShowErrorDialog(true)
|
||||
}
|
||||
|
||||
// We could also improve this by `rememberSaveable` to preserve the dialog after a configuration change. But the
|
||||
// dialog dismissing in such cases is not critical, and it would require creating StatusAction custom Saver
|
||||
val showStatusDialog = remember { mutableStateOf<StatusAction.Detailed?>(null) }
|
||||
|
||||
if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
|
||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||
|
@ -140,10 +150,12 @@ internal fun WrapBalances(
|
|||
balanceState = balanceState,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
isUpdateAvailable = isUpdateAvailable,
|
||||
isShowingErrorDialog = isShowingErrorDialog,
|
||||
isDetailedStatus = isDetailedSyncStatus,
|
||||
onSettings = goSettings,
|
||||
isShowingErrorDialog = isShowingErrorDialog,
|
||||
setShowErrorDialog = setShowErrorDialog,
|
||||
showStatusDialog = showStatusDialog.value,
|
||||
hideStatusDialog = { showStatusDialog.value = null },
|
||||
snackbarHostState = snackbarHostState,
|
||||
onShielding = {
|
||||
scope.launch {
|
||||
setShieldState(ShieldState.Running)
|
||||
|
@ -201,6 +213,21 @@ internal fun WrapBalances(
|
|||
}
|
||||
}
|
||||
},
|
||||
onStatusClick = { status ->
|
||||
when (status) {
|
||||
is StatusAction.Detailed -> showStatusDialog.value = status
|
||||
StatusAction.AppUpdate -> {
|
||||
openPlayStoreAppSite(
|
||||
context = context,
|
||||
snackbarHostState = snackbarHostState,
|
||||
scope = scope
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// No action required
|
||||
}
|
||||
}
|
||||
},
|
||||
shieldState = shieldState,
|
||||
walletSnapshot = walletSnapshot,
|
||||
walletRestoringState = walletRestoringState,
|
||||
|
@ -208,7 +235,7 @@ internal fun WrapBalances(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateTransparentBalanceState(
|
||||
private fun updateTransparentBalanceState(
|
||||
currentShieldState: ShieldState,
|
||||
walletSnapshot: WalletSnapshot?
|
||||
): ShieldState {
|
||||
|
@ -219,3 +246,20 @@ fun updateTransparentBalanceState(
|
|||
else -> currentShieldState
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPlayStoreAppSite(
|
||||
context: Context,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
scope: CoroutineScope
|
||||
) {
|
||||
val storeIntent = PlayStoreUtil.newActivityIntent(context)
|
||||
runCatching {
|
||||
context.startActivity(storeIntent)
|
||||
}.onFailure {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = context.getString(R.string.unable_to_open_play_store)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,22 @@ data class WalletDisplayValues(
|
|||
val progress: PercentDecimal,
|
||||
val zecAmountText: String,
|
||||
val statusText: String,
|
||||
val statusAction: StatusAction = StatusAction.None,
|
||||
val fiatCurrencyAmountState: FiatCurrencyConversionRateState,
|
||||
val fiatCurrencyAmountText: String
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
@Suppress("LongMethod")
|
||||
internal fun getNextValues(
|
||||
context: Context,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
isUpdateAvailable: Boolean = false,
|
||||
isDetailedStatus: Boolean = false,
|
||||
): WalletDisplayValues {
|
||||
var progress = PercentDecimal.ZERO_PERCENT
|
||||
val zecAmountText = walletSnapshot.totalBalance().toZecString()
|
||||
var statusText = ""
|
||||
var statusAction: StatusAction = StatusAction.None
|
||||
|
||||
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
||||
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
||||
// We'll ideally provide a "fresh" currencyConversion object here
|
||||
|
@ -55,63 +57,63 @@ data class WalletDisplayValues(
|
|||
)
|
||||
}
|
||||
statusText = context.getString(R.string.balances_status_syncing)
|
||||
statusAction = StatusAction.Syncing
|
||||
}
|
||||
Synchronizer.Status.SYNCED -> {
|
||||
statusText =
|
||||
if (isUpdateAvailable) {
|
||||
if (isUpdateAvailable) {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_update,
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
} else {
|
||||
context.getString(R.string.balances_status_synced)
|
||||
}
|
||||
statusAction = StatusAction.AppUpdate
|
||||
} else {
|
||||
statusText = context.getString(R.string.balances_status_synced)
|
||||
statusAction = StatusAction.Synced
|
||||
}
|
||||
}
|
||||
Synchronizer.Status.DISCONNECTED -> {
|
||||
if (isDetailedStatus) {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_error_detailed,
|
||||
context.getString(R.string.balances_status_error_detailed_connection)
|
||||
)
|
||||
} else {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_error_simple,
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
}
|
||||
}
|
||||
Synchronizer.Status.STOPPED -> {
|
||||
if (isDetailedStatus) {
|
||||
statusText = context.getString(R.string.balances_status_detailed_stopped)
|
||||
} else {
|
||||
statusText = context.getString(R.string.balances_status_syncing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// More detailed error message
|
||||
walletSnapshot.synchronizerError?.let {
|
||||
if (isDetailedStatus) {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_error_detailed,
|
||||
walletSnapshot.synchronizerError.getCauseMessage()
|
||||
?: context.getString(R.string.balances_status_error_detailed_unknown)
|
||||
)
|
||||
} else {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_error_simple,
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
statusAction =
|
||||
StatusAction.Disconnected(
|
||||
details = context.getString(R.string.balances_status_error_dialog_connection)
|
||||
)
|
||||
}
|
||||
Synchronizer.Status.STOPPED -> {
|
||||
statusText = context.getString(R.string.balances_status_syncing)
|
||||
statusAction =
|
||||
StatusAction.Stopped(
|
||||
details = context.getString(R.string.balances_status_dialog_stopped)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// More detailed error message
|
||||
walletSnapshot.synchronizerError?.let {
|
||||
statusText =
|
||||
context.getString(
|
||||
R.string.balances_status_error_simple,
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
statusAction =
|
||||
StatusAction.Error(
|
||||
details =
|
||||
context.getString(
|
||||
R.string.balances_status_error_dialog_cause,
|
||||
walletSnapshot.synchronizerError.getCauseMessage()
|
||||
?: context.getString(R.string.balances_status_error_dialog_unknown)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return WalletDisplayValues(
|
||||
progress = progress,
|
||||
zecAmountText = zecAmountText,
|
||||
statusAction = statusAction,
|
||||
statusText = statusText,
|
||||
fiatCurrencyAmountState = fiatCurrencyAmountState,
|
||||
fiatCurrencyAmountText = fiatCurrencyAmountText
|
||||
|
@ -120,6 +122,24 @@ data class WalletDisplayValues(
|
|||
}
|
||||
}
|
||||
|
||||
sealed class StatusAction {
|
||||
data object None : StatusAction()
|
||||
|
||||
data object Syncing : StatusAction()
|
||||
|
||||
data object Synced : StatusAction()
|
||||
|
||||
data object AppUpdate : StatusAction()
|
||||
|
||||
sealed class Detailed(open val details: String) : StatusAction()
|
||||
|
||||
data class Disconnected(override val details: String) : Detailed(details)
|
||||
|
||||
data class Stopped(override val details: String) : Detailed(details)
|
||||
|
||||
data class Error(override val details: String) : Detailed(details)
|
||||
}
|
||||
|
||||
private fun getFiatCurrencyRateValue(
|
||||
context: Context,
|
||||
fiatCurrencyAmountState: FiatCurrencyConversionRateState
|
||||
|
|
|
@ -26,6 +26,8 @@ import androidx.compose.material3.HorizontalDivider
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -55,6 +57,7 @@ import cash.z.ecc.sdk.type.ZcashCurrency
|
|||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
||||
import co.electriccoin.zcash.ui.common.compose.StatusDialog
|
||||
import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||
|
@ -81,6 +84,7 @@ import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
|
|||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.ShieldState
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
|
||||
|
||||
@Preview("Balances")
|
||||
|
@ -89,17 +93,20 @@ private fun ComposableBalancesPreview() {
|
|||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Balances(
|
||||
onSettings = {},
|
||||
isDetailedStatus = false,
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
isFiatConversionEnabled = false,
|
||||
isUpdateAvailable = false,
|
||||
isShowingErrorDialog = false,
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
setShowErrorDialog = {},
|
||||
onSettings = {},
|
||||
onShielding = {},
|
||||
onStatusClick = {},
|
||||
shieldState = ShieldState.Available,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
walletRestoringState = WalletRestoringState.NONE,
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -111,17 +118,20 @@ private fun ComposableBalancesShieldFailurePreview() {
|
|||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Balances(
|
||||
onSettings = {},
|
||||
isDetailedStatus = false,
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
isFiatConversionEnabled = false,
|
||||
isUpdateAvailable = false,
|
||||
isShowingErrorDialog = true,
|
||||
hideStatusDialog = {},
|
||||
showStatusDialog = null,
|
||||
setShowErrorDialog = {},
|
||||
onSettings = {},
|
||||
onShielding = {},
|
||||
onStatusClick = {},
|
||||
shieldState = ShieldState.Available,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
walletRestoringState = WalletRestoringState.NONE,
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -143,33 +153,41 @@ private fun ComposableBalancesShieldErrorDialogPreview() {
|
|||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun Balances(
|
||||
onSettings: () -> Unit,
|
||||
isDetailedStatus: Boolean,
|
||||
balanceState: BalanceState,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
isUpdateAvailable: Boolean,
|
||||
isShowingErrorDialog: Boolean,
|
||||
hideStatusDialog: () -> Unit,
|
||||
showStatusDialog: StatusAction.Detailed?,
|
||||
setShowErrorDialog: (Boolean) -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onShielding: () -> Unit,
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
shieldState: ShieldState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
walletSnapshot: WalletSnapshot?,
|
||||
walletRestoringState: WalletRestoringState,
|
||||
balanceState: BalanceState,
|
||||
) {
|
||||
Scaffold(topBar = {
|
||||
BalancesTopAppBar(
|
||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||
onSettings = onSettings
|
||||
)
|
||||
}) { paddingValues ->
|
||||
Scaffold(
|
||||
topBar = {
|
||||
BalancesTopAppBar(
|
||||
showRestoring = walletRestoringState == WalletRestoringState.RESTORING,
|
||||
onSettings = onSettings
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackbarHostState)
|
||||
},
|
||||
) { paddingValues ->
|
||||
if (null == walletSnapshot) {
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
BalancesMainContent(
|
||||
balanceState = balanceState,
|
||||
isDetailedStatus = isDetailedStatus,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
isUpdateAvailable = isUpdateAvailable,
|
||||
onShielding = onShielding,
|
||||
onStatusClick = onStatusClick,
|
||||
walletSnapshot = walletSnapshot,
|
||||
shieldState = shieldState,
|
||||
modifier =
|
||||
|
@ -182,6 +200,14 @@ fun Balances(
|
|||
walletRestoringState = walletRestoringState
|
||||
)
|
||||
|
||||
// Show synchronization status popup
|
||||
if (showStatusDialog != null) {
|
||||
StatusDialog(
|
||||
statusAction = showStatusDialog,
|
||||
onDone = hideStatusDialog
|
||||
)
|
||||
}
|
||||
|
||||
// Show shielding error popup
|
||||
if (isShowingErrorDialog && shieldState is ShieldState.Failed) {
|
||||
ShieldingErrorDialog(
|
||||
|
@ -257,10 +283,10 @@ private fun BalancesTopAppBar(
|
|||
@Composable
|
||||
private fun BalancesMainContent(
|
||||
balanceState: BalanceState,
|
||||
isDetailedStatus: Boolean,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
isUpdateAvailable: Boolean,
|
||||
onShielding: () -> Unit,
|
||||
onStatusClick: (StatusAction) -> Unit,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
shieldState: ShieldState,
|
||||
walletRestoringState: WalletRestoringState,
|
||||
|
@ -282,7 +308,7 @@ private fun BalancesMainContent(
|
|||
onReferenceClick = {}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||
|
||||
HorizontalDivider(
|
||||
color = ZcashTheme.colors.darkDividerColor,
|
||||
|
@ -304,11 +330,9 @@ private fun BalancesMainContent(
|
|||
walletSnapshot = walletSnapshot,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f, true))
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
if (walletRestoringState == WalletRestoringState.RESTORING) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Small(
|
||||
text = stringResource(id = R.string.balances_status_restoring_text),
|
||||
textFontWeight = FontWeight.Medium,
|
||||
|
@ -317,17 +341,20 @@ private fun BalancesMainContent(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingSmall)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
|
||||
}
|
||||
|
||||
SynchronizationStatus(
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = isUpdateAvailable,
|
||||
isDetailedStatus = isDetailedStatus,
|
||||
testTag = BalancesTag.STATUS
|
||||
onStatusClick = onStatusClick,
|
||||
testTag = BalancesTag.STATUS,
|
||||
walletSnapshot = walletSnapshot,
|
||||
modifier = Modifier.animateContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +544,6 @@ fun BalancesOverview(
|
|||
context = LocalContext.current,
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = false,
|
||||
isDetailedStatus = false
|
||||
)
|
||||
|
||||
Column(Modifier.testTag(BalancesTag.FIAT_CONVERSION)) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import cash.z.ecc.android.sdk.model.ZecSend
|
|||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.RestoreScreenBrightness
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
||||
|
@ -56,13 +55,6 @@ internal fun MainActivity.WrapHome(
|
|||
|
||||
val isRestoringInitialWarningSeen = homeViewModel.isRestoringInitialWarningSeen.collectAsStateWithLifecycle().value
|
||||
|
||||
// Detailed sync status info is used if set in configuration or if the app is built as debuggable
|
||||
// (i.e. mainly in development)
|
||||
val isDetailedSyncStatus =
|
||||
homeViewModel.isDetailedSyncStatus.collectAsStateWithLifecycle().value.run {
|
||||
this ?: false || VersionInfo.new(this@WrapHome).isDebuggable
|
||||
}
|
||||
|
||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
||||
|
||||
val walletRestoringState = walletViewModel.walletRestoringState.collectAsStateWithLifecycle().value
|
||||
|
@ -98,7 +90,6 @@ internal fun MainActivity.WrapHome(
|
|||
goSettings = goSettings,
|
||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
|
||||
homeScreenIndex = homeScreenIndex,
|
||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
||||
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
||||
isShowingRestoreInitDialog = isShowingRestoreInitDialog,
|
||||
onPageChange = {
|
||||
|
@ -120,7 +111,6 @@ internal fun WrapHome(
|
|||
goScan: () -> Unit,
|
||||
goSendConfirmation: (ZecSend) -> Unit,
|
||||
homeScreenIndex: HomeScreenIndex,
|
||||
isDetailedSyncStatus: Boolean,
|
||||
isKeepScreenOnWhileSyncing: Boolean?,
|
||||
isShowingRestoreInitDialog: Boolean,
|
||||
onPageChange: (HomeScreenIndex) -> Unit,
|
||||
|
@ -203,7 +193,6 @@ internal fun WrapHome(
|
|||
screenContent = {
|
||||
WrapBalances(
|
||||
activity = activity,
|
||||
isDetailedSyncStatus = isDetailedSyncStatus,
|
||||
goSettings = goSettings,
|
||||
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure
|
||||
)
|
||||
|
|
|
@ -16,9 +16,9 @@ import co.electriccoin.zcash.ui.R
|
|||
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import co.electriccoin.zcash.ui.screen.update.util.PlayStoreUtil
|
||||
import co.electriccoin.zcash.ui.screen.update.view.Update
|
||||
import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel
|
||||
import co.electriccoin.zcash.ui.util.PlayStoreUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -111,7 +111,7 @@ internal fun WrapUpdate(
|
|||
},
|
||||
onLater = onLaterAction,
|
||||
onReference = {
|
||||
openPlayStoreAppPage(
|
||||
openPlayStoreAppSite(
|
||||
activity.applicationContext,
|
||||
snackbarHostState,
|
||||
scope
|
||||
|
@ -120,7 +120,7 @@ internal fun WrapUpdate(
|
|||
)
|
||||
}
|
||||
|
||||
fun openPlayStoreAppPage(
|
||||
private fun openPlayStoreAppSite(
|
||||
context: Context,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
scope: CoroutineScope
|
||||
|
@ -131,7 +131,7 @@ fun openPlayStoreAppPage(
|
|||
}.onFailure {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = context.getString(R.string.update_unable_to_open_play_store)
|
||||
message = context.getString(R.string.unable_to_open_play_store)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ private fun UpdateContentContent(
|
|||
} else {
|
||||
ImageVector.vectorResource(R.drawable.ic_zashi_logo_update_available)
|
||||
},
|
||||
contentDescription = stringResource(id = R.string.update_image_content_description)
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingBig))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui.screen.update.util
|
||||
package co.electriccoin.zcash.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
|
@ -22,14 +22,23 @@
|
|||
<string name="balances_status_synced">Synced</string>
|
||||
<string name="balances_status_update">Please update <xliff:g id="app_name" example="Zashi">%1$s</xliff:g> using Google Play</string>
|
||||
<string name="balances_status_error_simple"><xliff:g id="app_name" example="Zashi">%1$s</xliff:g> encountered an error while syncing, attempting to resolve…</string>
|
||||
<string name="balances_status_error_detailed" formatted="true">Error: <xliff:g id="error_type" example="Lost connection">%1$s</xliff:g></string>
|
||||
<string name="balances_status_error_detailed_connection">Disconnected</string>
|
||||
<string name="balances_status_error_detailed_unknown">Unknown cause</string>
|
||||
<string name="balances_status_detailed_stopped">Synchronizer stopped</string>
|
||||
<string name="balances_status_restoring_text">The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.</string>
|
||||
|
||||
<string name="balances_shielding_successful">Shielding has been successfully submitted</string>
|
||||
|
||||
<string name="balances_status_error_dialog_title">Error</string>
|
||||
<string name="balances_status_error_dialog_connection">
|
||||
Disconnected. Please check your internet connection.
|
||||
</string>
|
||||
<string name="balances_status_error_dialog_cause" formatted="true">
|
||||
Error: <xliff:g id="error_cause" example="Block scanning problem">%1$s</xliff:g>
|
||||
</string>
|
||||
<string name="balances_status_error_dialog_unknown">
|
||||
Unknown cause. Please contact our support team if the problem persists.
|
||||
</string>
|
||||
<string name="balances_status_dialog_stopped">Synchronization is stopped. It will resume soon.</string>
|
||||
<string name="balances_status_dialog_button">OK</string>
|
||||
|
||||
<string name="balances_shielding_dialog_error_title">Failed to shield funds</string>
|
||||
<string name="balances_shielding_dialog_error_text">Error: The attempt to shield the transparent funds failed. Try it again, please.</string>
|
||||
<string name="balances_shielding_dialog_error_btn">OK</string>
|
||||
|
|
|
@ -7,4 +7,5 @@
|
|||
<!-- This is replaced by a resource overlay via app/build.gradle.kts -->
|
||||
<string name="support_email_address" />
|
||||
<string name="restoring_wallet_label">[Restoring Your Wallet…]</string>
|
||||
<string name="unable_to_open_play_store">Unable to launch Google Play store app…</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="update_header">Update available</string>
|
||||
<string name="update_critical_header">Update required</string>
|
||||
<string name="update_image_content_description"></string>
|
||||
<string name="update_title_available"><xliff:g id="app_name" example="Zcash">%1$s</xliff:g> here.</string>
|
||||
<string name="update_title_required">It\'s not you, it\'s me.</string>
|
||||
<string name="update_description_required">
|
||||
|
@ -17,5 +16,4 @@
|
|||
<string name="update_download_button">Update</string>
|
||||
<string name="update_later_enabled_button">Remind me later</string>
|
||||
<string name="update_later_disabled_button">(required)</string>
|
||||
<string name="update_unable_to_open_play_store">Unable to launch Google Play store app.</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue