parent
af92f1b52f
commit
0d3d0c4d19
|
@ -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
|
||||
|
|
|
@ -35,6 +35,7 @@ class BalancesTestSetup(
|
|||
onSettings = {
|
||||
onSettingsCount.incrementAndGet()
|
||||
},
|
||||
isDetailedStatus = false,
|
||||
isFiatConversionEnabled = isShowFiatConversion,
|
||||
isUpdateAvailable = false,
|
||||
isShowingErrorDialog = false,
|
||||
|
|
|
@ -29,9 +29,10 @@ class WalletDisplayValuesTest {
|
|||
)
|
||||
val values =
|
||||
WalletDisplayValues.getNextValues(
|
||||
getAppContext(),
|
||||
walletSnapshot,
|
||||
false
|
||||
context = getAppContext(),
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = false,
|
||||
isDetailedStatus = false
|
||||
)
|
||||
|
||||
assertNotNull(values)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue