From 7e9e89725bf221388a70a4ab2eefcfafe7b4787f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Rychnovsk=C3=BD?= Date: Fri, 24 May 2024 10:41:57 +0200 Subject: [PATCH] [#1449] Display Synchronizer details in dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CHANGELOG.md | 8 ++ .../zcash/ui/design/component/Dialog.kt | 5 +- .../zcash/ui/design/theme/Dimens.kt | 2 + .../ui/screen/account/AccountTestSetup.kt | 5 + .../account/history/HistoryTestSetup.kt | 1 + .../ui/screen/balances/BalancesTestSetup.kt | 6 +- .../balances/model/WalletDisplayValuesTest.kt | 3 +- .../screen/update/util/PlayStoreUtilTest.kt | 1 + .../zcash/ui/common/compose/BalanceWidget.kt | 14 ++- .../common/compose/SynchronizationStatus.kt | 47 ++++++-- .../viewmodel/AuthenticationViewModel.kt | 9 +- .../zcash/ui/screen/account/AndroidAccount.kt | 54 +++++++++- .../ui/screen/account/view/AccountView.kt | 48 +++++++-- .../ui/screen/account/view/HistoryView.kt | 17 ++- .../ui/screen/balances/AndroidBalances.kt | 58 ++++++++-- .../balances/model/WalletDisplayValues.kt | 100 +++++++++++------- .../ui/screen/balances/view/BalancesView.kt | 80 +++++++++----- .../zcash/ui/screen/home/AndroidHome.kt | 11 -- .../zcash/ui/screen/update/AndroidUpdate.kt | 8 +- .../zcash/ui/screen/update/view/UpdateView.kt | 2 +- .../{screen/update => }/util/PlayStoreUtil.kt | 2 +- .../main/res/ui/balances/values/strings.xml | 17 ++- .../src/main/res/ui/common/values/strings.xml | 1 + .../src/main/res/ui/update/values/strings.xml | 2 - 24 files changed, 368 insertions(+), 133 deletions(-) rename ui-lib/src/main/java/co/electriccoin/zcash/ui/{screen/update => }/util/PlayStoreUtil.kt (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0698b653..43a0c6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Dialog.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Dialog.kt index 7429bd4b..f85120c0 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Dialog.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/Dialog.kt @@ -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 { diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/Dimens.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/Dimens.kt index 852821ab..0e287e34 100644 --- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/Dimens.kt +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/theme/Dimens.kt @@ -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, diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/AccountTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/AccountTestSetup.kt index 0ee8bf34..2c0d731a 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/AccountTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/AccountTestSetup.kt @@ -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() ) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/history/HistoryTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/history/HistoryTestSetup.kt index e461766f..9f3a226c 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/history/HistoryTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/account/history/HistoryTestSetup.kt @@ -30,6 +30,7 @@ class HistoryTestSetup( ZcashTheme { HistoryContainer( transactionState = initialHistoryUiState, + onStatusClick = {}, onTransactionItemAction = { onItemIdClickCount.incrementAndGet() }, diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/BalancesTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/BalancesTestSetup.kt index 585f5ad8..911259af 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/BalancesTestSetup.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/BalancesTestSetup.kt @@ -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, diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValuesTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValuesTest.kt index be030e89..f132fddc 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValuesTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValuesTest.kt @@ -31,8 +31,7 @@ class WalletDisplayValuesTest { WalletDisplayValues.getNextValues( context = getAppContext(), walletSnapshot = walletSnapshot, - isUpdateAvailable = false, - isDetailedStatus = false + isUpdateAvailable = false ) assertNotNull(values) diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtilTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtilTest.kt index 18718be6..2a7329b9 100644 --- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtilTest.kt +++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtilTest.kt @@ -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 diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/BalanceWidget.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/BalanceWidget.kt index 672be782..9819bce8 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/BalanceWidget.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/BalanceWidget.kt @@ -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, + ) ) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/SynchronizationStatus.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/SynchronizationStatus.kt index 28fd687e..84cbe13d 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/SynchronizationStatus.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/compose/SynchronizationStatus.kt @@ -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 + ) +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt index c2d5d3bb..6e4148db 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/common/viewmodel/AuthenticationViewModel.kt @@ -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() } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt index 187f5fc2..5aff9689 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/AndroidAccount.kt @@ -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() val transactionHistoryViewModel by activity.viewModels() @@ -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(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) + ) + } + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/AccountView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/AccountView.kt index ce07d9dd..bf7c975d 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/AccountView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/AccountView.kt @@ -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, diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt index 02189f13..2d0a340d 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt @@ -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)) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt index a7f8db7b..fba38500 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/AndroidBalances.kt @@ -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(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) + ) + } + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValues.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValues.kt index 48102e1b..c2eb6bcd 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValues.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/model/WalletDisplayValues.kt @@ -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 diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/view/BalancesView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/view/BalancesView.kt index 3f00a7c4..c8b67f02 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/view/BalancesView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/balances/view/BalancesView.kt @@ -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)) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt index 1ee69b7f..e5d17def 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/home/AndroidHome.kt @@ -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 ) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt index ef017190..3761033c 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/AndroidUpdate.kt @@ -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) ) } } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/view/UpdateView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/view/UpdateView.kt index 6f2fa3f7..56641752 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/view/UpdateView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/view/UpdateView.kt @@ -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)) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtil.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/util/PlayStoreUtil.kt similarity index 94% rename from ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtil.kt rename to ui-lib/src/main/java/co/electriccoin/zcash/ui/util/PlayStoreUtil.kt index ba4e3393..2d23efb4 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/update/util/PlayStoreUtil.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/util/PlayStoreUtil.kt @@ -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 diff --git a/ui-lib/src/main/res/ui/balances/values/strings.xml b/ui-lib/src/main/res/ui/balances/values/strings.xml index 16b325f2..51f64b92 100644 --- a/ui-lib/src/main/res/ui/balances/values/strings.xml +++ b/ui-lib/src/main/res/ui/balances/values/strings.xml @@ -22,14 +22,23 @@ Synced Please update %1$s using Google Play %1$s encountered an error while syncing, attempting to resolve… - Error: %1$s - Disconnected - Unknown cause - Synchronizer stopped The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour. Shielding has been successfully submitted + Error + + Disconnected. Please check your internet connection. + + + Error: %1$s + + + Unknown cause. Please contact our support team if the problem persists. + + Synchronization is stopped. It will resume soon. + OK + Failed to shield funds Error: The attempt to shield the transparent funds failed. Try it again, please. OK diff --git a/ui-lib/src/main/res/ui/common/values/strings.xml b/ui-lib/src/main/res/ui/common/values/strings.xml index 3f9e1420..80f932a5 100644 --- a/ui-lib/src/main/res/ui/common/values/strings.xml +++ b/ui-lib/src/main/res/ui/common/values/strings.xml @@ -7,4 +7,5 @@ [Restoring Your Wallet…] + Unable to launch Google Play store app… diff --git a/ui-lib/src/main/res/ui/update/values/strings.xml b/ui-lib/src/main/res/ui/update/values/strings.xml index 78539d9a..8176d254 100644 --- a/ui-lib/src/main/res/ui/update/values/strings.xml +++ b/ui-lib/src/main/res/ui/update/values/strings.xml @@ -2,7 +2,6 @@ Update available Update required - %1$s here. It\'s not you, it\'s me. @@ -17,5 +16,4 @@ Update Remind me later (required) - Unable to launch Google Play store app.