[#1263] Statuses of the sync process

- Closes #1263
- Changelog update
This commit is contained in:
Honza Rychnovský 2024-04-09 12:19:13 +02:00 committed by GitHub
parent af92f1b52f
commit 0d3d0c4d19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 106 additions and 53 deletions

View File

@ -31,6 +31,7 @@ directly impact users rather than highlighting other key architectural updates.*
- The error dialog contains an error description now. It's useful for tracking down the failure cause.
- A small circular progress indicator is displayed when the app runs block synchronization, and the available balance
is zero instead of reflecting a result value.
- Block synchronization statuses have been simplified to Syncing, Synced, and Error states only
### Fixed
- Button sizing has been updated to align with the design guidelines and preserve stretching if necessary

View File

@ -35,6 +35,7 @@ class BalancesTestSetup(
onSettings = {
onSettingsCount.incrementAndGet()
},
isDetailedStatus = false,
isFiatConversionEnabled = isShowFiatConversion,
isUpdateAvailable = false,
isShowingErrorDialog = false,

View File

@ -29,9 +29,10 @@ class WalletDisplayValuesTest {
)
val values =
WalletDisplayValues.getNextValues(
getAppContext(),
walletSnapshot,
false
context = getAppContext(),
walletSnapshot = walletSnapshot,
isUpdateAvailable = false,
isDetailedStatus = false
)
assertNotNull(values)

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.configuration.model.map.Configuration
import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
@ -12,6 +13,7 @@ import co.electriccoin.zcash.ui.screen.home.HomeScreenIndex
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
@ -24,13 +26,31 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
/**
* A flow of whether background sync is enabled
* Current Home sub-screen index in flow.
*/
val isBackgroundSyncEnabled: StateFlow<Boolean?> =
flow {
val preferenceProvider = StandardPreferenceSingleton.getInstance(application)
emitAll(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED.observe(preferenceProvider))
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT.inWholeMilliseconds), null)
booleanStateFlow(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED)
/**
* A flow of whether keep screen on while syncing is on or off
*/
val isKeepScreenOnWhileSyncing: StateFlow<Boolean?> =
booleanStateFlow(StandardPreferenceKeys.IS_KEEP_SCREEN_ON_DURING_SYNC)
/**
* A flow of whether the app uses simple or detailed block synchronization status information for the UI
*/
val isDetailedSyncStatus: StateFlow<Boolean?> =
booleanStateFlow(StandardPreferenceKeys.IS_DETAILED_SYNC_STATUS)
private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow<Boolean?> =
flow<Boolean?> {
val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication())
emitAll(default.observe(preferenceProvider))
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
val configurationFlow: StateFlow<Configuration?> =
AndroidConfigurationFactory.getInstance(application).getConfigurationFlow()

View File

@ -26,13 +26,14 @@ object StandardPreferenceKeys {
WalletRestoringState.RESTORING.toNumber()
)
// Default to true until https://github.com/Electric-Coin-Company/zashi-android/issues/304
val IS_ANALYTICS_ENABLED = BooleanPreferenceDefault(PreferenceKey("is_analytics_enabled"), true)
val IS_BACKGROUND_SYNC_ENABLED = BooleanPreferenceDefault(PreferenceKey("is_background_sync_enabled"), true)
val IS_KEEP_SCREEN_ON_DURING_SYNC = BooleanPreferenceDefault(PreferenceKey("is_keep_screen_on_during_sync"), true)
val IS_DETAILED_SYNC_STATUS = BooleanPreferenceDefault(PreferenceKey("is_detailed_sync_status"), false)
/**
* The fiat currency that the user prefers.
*/

View File

@ -37,6 +37,7 @@ import org.jetbrains.annotations.VisibleForTesting
@Composable
internal fun WrapBalances(
activity: ComponentActivity,
isDetailedSyncStatus: Boolean,
goSettings: () -> Unit,
goMultiTrxSubmissionFailure: () -> Unit,
) {
@ -63,10 +64,11 @@ internal fun WrapBalances(
WrapBalances(
balanceState = balanceState,
checkUpdateViewModel = checkUpdateViewModel,
createTransactionsViewModel = createTransactionsViewModel,
checkUpdateViewModel = checkUpdateViewModel,
goSettings = goSettings,
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
isDetailedSyncStatus = isDetailedSyncStatus,
spendingKey = spendingKey,
synchronizer = synchronizer,
walletSnapshot = walletSnapshot,
@ -85,6 +87,7 @@ internal fun WrapBalances(
createTransactionsViewModel: CreateTransactionsViewModel,
goSettings: () -> Unit,
goMultiTrxSubmissionFailure: () -> Unit,
isDetailedSyncStatus: Boolean,
spendingKey: UnifiedSpendingKey?,
synchronizer: Synchronizer?,
walletSnapshot: WalletSnapshot?,
@ -129,10 +132,11 @@ internal fun WrapBalances(
} else {
Balances(
balanceState = balanceState,
onSettings = goSettings,
isFiatConversionEnabled = isFiatConversionEnabled,
isUpdateAvailable = isUpdateAvailable,
isShowingErrorDialog = isShowingErrorDialog,
isDetailedStatus = isDetailedSyncStatus,
onSettings = goSettings,
setShowErrorDialog = setShowErrorDialog,
onShielding = {
scope.launch {

View File

@ -26,7 +26,8 @@ data class WalletDisplayValues(
internal fun getNextValues(
context: Context,
walletSnapshot: WalletSnapshot,
updateAvailable: Boolean = false
isUpdateAvailable: Boolean = false,
isDetailedStatus: Boolean = false,
): WalletDisplayValues {
var progress = PercentDecimal.ZERO_PERCENT
val zecAmountText = walletSnapshot.totalBalance().toZecString()
@ -59,32 +60,47 @@ data class WalletDisplayValues(
}
Synchronizer.Status.SYNCED -> {
statusText =
if (updateAvailable) {
context.getString(R.string.balances_status_update)
if (isUpdateAvailable) {
context.getString(
R.string.balances_status_update,
context.getString(R.string.app_name)
)
} else {
context.getString(R.string.balances_status_synced)
}
}
Synchronizer.Status.DISCONNECTED -> {
statusText =
context.getString(
R.string.balances_status_error,
context.getString(R.string.balances_status_error_connection)
)
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)
}
}
Synchronizer.Status.STOPPED -> {
statusText = context.getString(R.string.balances_status_stopped)
if (isDetailedStatus) {
statusText = context.getString(R.string.balances_status_stopped)
} else {
statusText = context.getString(R.string.balances_status_syncing)
}
}
}
// More detailed error message
walletSnapshot.synchronizerError?.let {
statusText =
context.getString(
R.string.balances_status_error,
walletSnapshot.synchronizerError.getCauseMessage()
?: context.getString(R.string.balances_status_error_unknown)
)
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)
}
}
return WalletDisplayValues(

View File

@ -86,6 +86,7 @@ private fun ComposableBalancesPreview() {
GradientSurface {
Balances(
onSettings = {},
isDetailedStatus = false,
isFiatConversionEnabled = false,
isUpdateAvailable = false,
isShowingErrorDialog = false,
@ -94,7 +95,7 @@ private fun ComposableBalancesPreview() {
shieldState = ShieldState.Available,
walletSnapshot = WalletSnapshotFixture.new(),
walletRestoringState = WalletRestoringState.NONE,
balanceState = BalanceStateFixture.new()
balanceState = BalanceStateFixture.new(),
)
}
}
@ -107,6 +108,7 @@ private fun ComposableBalancesShieldFailurePreview() {
GradientSurface {
Balances(
onSettings = {},
isDetailedStatus = false,
isFiatConversionEnabled = false,
isUpdateAvailable = false,
isShowingErrorDialog = true,
@ -115,7 +117,7 @@ private fun ComposableBalancesShieldFailurePreview() {
shieldState = ShieldState.Available,
walletSnapshot = WalletSnapshotFixture.new(),
walletRestoringState = WalletRestoringState.NONE,
balanceState = BalanceStateFixture.new()
balanceState = BalanceStateFixture.new(),
)
}
}
@ -125,6 +127,7 @@ private fun ComposableBalancesShieldFailurePreview() {
@Composable
fun Balances(
onSettings: () -> Unit,
isDetailedStatus: Boolean,
isFiatConversionEnabled: Boolean,
isUpdateAvailable: Boolean,
isShowingErrorDialog: Boolean,
@ -146,6 +149,7 @@ fun Balances(
} else {
BalancesMainContent(
balanceState = balanceState,
isDetailedStatus = isDetailedStatus,
isFiatConversionEnabled = isFiatConversionEnabled,
isUpdateAvailable = isUpdateAvailable,
onShielding = onShielding,
@ -235,6 +239,7 @@ private fun BalancesTopAppBar(
@Composable
private fun BalancesMainContent(
balanceState: BalanceState,
isDetailedStatus: Boolean,
isFiatConversionEnabled: Boolean,
isUpdateAvailable: Boolean,
onShielding: () -> Unit,
@ -287,6 +292,7 @@ private fun BalancesMainContent(
SyncStatus(
walletSnapshot = walletSnapshot,
isUpdateAvailable = isUpdateAvailable,
isDetailedStatus = isDetailedStatus,
)
}
}
@ -465,9 +471,10 @@ fun BalancesOverview(
if (isFiatConversionEnabled) {
val walletDisplayValues =
WalletDisplayValues.getNextValues(
LocalContext.current,
walletSnapshot,
false
context = LocalContext.current,
walletSnapshot = walletSnapshot,
isUpdateAvailable = false,
isDetailedStatus = false
)
Column(Modifier.testTag(BalancesTag.FIAT_CONVERSION)) {
@ -606,13 +613,15 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
@Composable
fun SyncStatus(
isUpdateAvailable: Boolean,
isDetailedStatus: Boolean,
walletSnapshot: WalletSnapshot,
) {
val walletDisplayValues =
WalletDisplayValues.getNextValues(
LocalContext.current,
walletSnapshot,
isUpdateAvailable
context = LocalContext.current,
walletSnapshot = walletSnapshot,
isUpdateAvailable = isUpdateAvailable,
isDetailedStatus = isDetailedStatus
)
Column(
@ -621,7 +630,8 @@ fun SyncStatus(
if (walletDisplayValues.statusText.isNotEmpty()) {
BodySmall(
text = walletDisplayValues.statusText,
modifier = Modifier.testTag(BalancesTag.STATUS)
modifier = Modifier.testTag(BalancesTag.STATUS),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))

View File

@ -13,6 +13,7 @@ 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
@ -25,7 +26,6 @@ import co.electriccoin.zcash.ui.screen.home.view.Home
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
import co.electriccoin.zcash.ui.screen.send.WrapSend
import co.electriccoin.zcash.ui.screen.send.model.SendArguments
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -44,11 +44,16 @@ internal fun MainActivity.WrapHome(
val walletViewModel by viewModels<WalletViewModel>()
val settingsViewModel by viewModels<SettingsViewModel>()
val homeScreenIndex = homeViewModel.screenIndex.collectAsStateWithLifecycle().value
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
val isKeepScreenOnWhileSyncing = homeViewModel.isKeepScreenOnWhileSyncing.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
@ -67,6 +72,7 @@ internal fun MainActivity.WrapHome(
goSettings = goSettings,
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure,
homeScreenIndex = homeScreenIndex,
isDetailedSyncStatus = isDetailedSyncStatus,
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
onPageChange = {
homeViewModel.screenIndex.value = it
@ -86,6 +92,7 @@ internal fun WrapHome(
goScan: () -> Unit,
goSendConfirmation: (ZecSend) -> Unit,
homeScreenIndex: HomeScreenIndex,
isDetailedSyncStatus: Boolean,
isKeepScreenOnWhileSyncing: Boolean?,
onPageChange: (HomeScreenIndex) -> Unit,
sendArguments: SendArguments,
@ -166,6 +173,7 @@ internal fun WrapHome(
screenContent = {
WrapBalances(
activity = activity,
isDetailedSyncStatus = isDetailedSyncStatus,
goSettings = goSettings,
goMultiTrxSubmissionFailure = goMultiTrxSubmissionFailure
)

View File

@ -319,10 +319,6 @@ private fun RestoreSeedBirthdayTopAppBar(
)
}
// TODO [#672]: Implement custom seed phrase pasting for wallet import
// TODO [#672]: https://github.com/Electric-Coin-Company/zashi-android/issues/672
// TODO [#1060]: https://github.com/Electric-Coin-Company/zashi-android/issues/1060
@OptIn(ExperimentalComposeUiApi::class)
@Suppress("UNUSED_PARAMETER", "LongParameterList", "LongMethod")
@Composable

View File

@ -18,17 +18,12 @@
<string name="balances_status_syncing_percentage" formatted="true"><xliff:g id="synced_percent" example="50.25">
%1$s</xliff:g>%%</string> <!-- double %% for escaping -->
<string name="balances_status_synced">Synced</string>
<string name="balances_status_sending_format" formatted="true">Sending <xliff:g id="sending_amount" example=".023">%1$s</xliff:g></string>
<string name="balances_status_receiving_format" formatted="true">Receiving <xliff:g id="receiving_amount" example=".023">%1$s</xliff:g> ZEC</string>
<string name="balances_status_shielding_format" formatted="true">Shielding <xliff:g id="shielding_amount" example=".023">%1$s</xliff:g> ZEC</string>
<string name="balances_status_update">Please Update via Play Store</string>
<string name="balances_status_error" formatted="true">Error: <xliff:g id="error_type" example="Lost connection">%1$s</xliff:g></string>
<string name="balances_status_error_connection">Disconnected</string>
<string name="balances_status_error_unknown">Unknown cause</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_stopped">Synchronizer stopped</string>
<string name="balances_status_updating_blockheight">Updating blockheight</string>
<string name="balances_status_fiat_currency_price_out_of_date" formatted="true"><xliff:g id="fiat_currency" example="USD">%1$s</xliff:g> price out-of-date</string>
<string name="balances_status_spendable" formatted="true">Fully spendable in <xliff:g id="spendable_time" example="2 minutes">%1$s</xliff:g></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>