[#1319] Restoring state UI in Account and Balances (#1334)

* [#1319] Restoring state UI in Account and Balances

- Closes #1319

* [#1319] Restoring state widget in Account

- Closes #1319

* Add syncing widget to Account while restoring

* Update changelog

Plus reset the previously unreleased app record
This commit is contained in:
Honza Rychnovský 2024-04-12 11:55:44 +02:00 committed by GitHub
parent bcdf328cb4
commit cad8e48bf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 197 additions and 74 deletions

View File

@ -9,8 +9,6 @@ directly impact users rather than highlighting other key architectural updates.*
## [Unreleased] ## [Unreleased]
## [0.2.0 (594)] - 2024-04-09
### Added ### Added
- Advanced Settings screen that provides more technical options like Export private data, Recovery phrase, or - Advanced Settings screen that provides more technical options like Export private data, Recovery phrase, or
Choose server has been added Choose server has been added
@ -21,8 +19,8 @@ directly impact users rather than highlighting other key architectural updates.*
- Transitions between screens are now animated with a simple slide animation - Transitions between screens are now animated with a simple slide animation
- Proposal API from the Zcash SDK has been integrated together with handling error states for multi-transaction - Proposal API from the Zcash SDK has been integrated together with handling error states for multi-transaction
submission submission
- New Restoring Your Wallet label has been added to all post-onboarding screens to notify users about the current - New Restoring Your Wallet label and Synchronization widget have been added to all post-onboarding screens to notify
state of the wallet-restoring process. users about the current state of the wallet-restoring process
### Changed ### Changed
- The Transaction History UI has been incorporated into the Account screen and completely reworked according to the - The Transaction History UI has been incorporated into the Account screen and completely reworked according to the

View File

@ -192,6 +192,7 @@ fun TitleLarge(
fun Small( fun Small(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
textFontWeight: FontWeight = FontWeight.Normal,
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip, overflow: TextOverflow = TextOverflow.Clip,
textAlign: TextAlign = TextAlign.Start, textAlign: TextAlign = TextAlign.Start,
@ -200,6 +201,7 @@ fun Small(
Text( Text(
text = text, text = text,
color = color, color = color,
fontWeight = textFontWeight,
maxLines = maxLines, maxLines = maxLines,
overflow = overflow, overflow = overflow,
textAlign = textAlign, textAlign = textAlign,

View File

@ -26,6 +26,7 @@ data class ExtendedColors(
val textDisabled: Color, val textDisabled: Color,
val textFieldHint: Color, val textFieldHint: Color,
val textFieldError: Color, val textFieldError: Color,
val textFieldWarning: Color,
val textFieldFrame: Color, val textFieldFrame: Color,
val textDescription: Color, val textDescription: Color,
val textPending: Color, val textPending: Color,
@ -48,6 +49,7 @@ data class ExtendedColors(
val radioButtonTextColor: Color, val radioButtonTextColor: Color,
val historyBackgroundColor: Color, val historyBackgroundColor: Color,
val historyRedColor: Color, val historyRedColor: Color,
val historySyncingColor: Color,
) { ) {
@Composable @Composable
fun surfaceGradient() = fun surfaceGradient() =

View File

@ -29,6 +29,7 @@ internal object Dark {
val textChipIndex = Color(0xFFFFB900) val textChipIndex = Color(0xFFFFB900)
val textFieldFrame = Color(0xFF231F20) val textFieldFrame = Color(0xFF231F20)
val textFieldError = Color(0xFFFF0000) val textFieldError = Color(0xFFFF0000)
val textFieldWarning = Color(0xFFF40202)
val textFieldHint = Color(0xFFB7B7B7) val textFieldHint = Color(0xFFB7B7B7)
val textDescription = Color(0xFF777777) val textDescription = Color(0xFF777777)
val textProgress = Color(0xFF8B8A8A) val textProgress = Color(0xFF8B8A8A)
@ -71,7 +72,8 @@ internal object Dark {
val buttonShadowColor = Color(0xFFFFFFFF) val buttonShadowColor = Color(0xFFFFFFFF)
val historyBackgroundColor = Color(0xFFF6F6F6) val historyBackgroundColor = Color(0xFFF6F6F6)
val historyRedColor = Color(0xFFF40202) val historyRedColor = textFieldWarning
val historySyncingColor = panelBackgroundColor
} }
internal object Light { internal object Light {
@ -88,6 +90,7 @@ internal object Light {
val textDisabled = Color(0xFFB7B7B7) val textDisabled = Color(0xFFB7B7B7)
val textFieldFrame = Color(0xFF231F20) val textFieldFrame = Color(0xFF231F20)
val textFieldError = Color(0xFFCD0002) val textFieldError = Color(0xFFCD0002)
val textFieldWarning = Color(0xFFF40202)
val textFieldHint = Color(0xFFB7B7B7) val textFieldHint = Color(0xFFB7B7B7)
val textChipIndex = Color(0xFFEE8592) val textChipIndex = Color(0xFFEE8592)
val textDescription = Color(0xFF777777) val textDescription = Color(0xFF777777)
@ -130,7 +133,8 @@ internal object Light {
val buttonShadowColor = Color(0xFF000000) val buttonShadowColor = Color(0xFF000000)
val historyBackgroundColor = Color(0xFFF6F6F6) val historyBackgroundColor = Color(0xFFF6F6F6)
val historyRedColor = Color(0xFFF40202) val historyRedColor = textFieldWarning
val historySyncingColor = Dark.panelBackgroundColor
} }
internal val DarkColorPalette = internal val DarkColorPalette =
@ -177,6 +181,7 @@ internal val DarkExtendedColorPalette =
textDisabled = Dark.textDisabled, textDisabled = Dark.textDisabled,
textFieldFrame = Dark.textFieldFrame, textFieldFrame = Dark.textFieldFrame,
textFieldError = Dark.textFieldError, textFieldError = Dark.textFieldError,
textFieldWarning = Dark.textFieldWarning,
textFieldHint = Dark.textFieldHint, textFieldHint = Dark.textFieldHint,
textDescription = Dark.textDescription, textDescription = Dark.textDescription,
textPending = Dark.textProgress, textPending = Dark.textProgress,
@ -199,6 +204,7 @@ internal val DarkExtendedColorPalette =
radioButtonTextColor = Dark.radioButtonTextColor, radioButtonTextColor = Dark.radioButtonTextColor,
historyBackgroundColor = Dark.historyBackgroundColor, historyBackgroundColor = Dark.historyBackgroundColor,
historyRedColor = Dark.historyRedColor, historyRedColor = Dark.historyRedColor,
historySyncingColor = Dark.historySyncingColor,
) )
internal val LightExtendedColorPalette = internal val LightExtendedColorPalette =
@ -221,6 +227,7 @@ internal val LightExtendedColorPalette =
textDisabled = Light.textDisabled, textDisabled = Light.textDisabled,
textFieldFrame = Light.textFieldFrame, textFieldFrame = Light.textFieldFrame,
textFieldError = Light.textFieldError, textFieldError = Light.textFieldError,
textFieldWarning = Light.textFieldWarning,
textFieldHint = Light.textFieldHint, textFieldHint = Light.textFieldHint,
textDescription = Light.textDescription, textDescription = Light.textDescription,
textPending = Light.textProgress, textPending = Light.textProgress,
@ -243,6 +250,7 @@ internal val LightExtendedColorPalette =
radioButtonTextColor = Light.radioButtonTextColor, radioButtonTextColor = Light.radioButtonTextColor,
historyBackgroundColor = Light.historyBackgroundColor, historyBackgroundColor = Light.historyBackgroundColor,
historyRedColor = Light.historyRedColor, historyRedColor = Light.historyRedColor,
historySyncingColor = Light.historySyncingColor,
) )
@Suppress("CompositionLocalAllowlist") @Suppress("CompositionLocalAllowlist")
@ -267,6 +275,7 @@ internal val LocalExtendedColors =
textDisabled = Color.Unspecified, textDisabled = Color.Unspecified,
textFieldHint = Color.Unspecified, textFieldHint = Color.Unspecified,
textFieldError = Color.Unspecified, textFieldError = Color.Unspecified,
textFieldWarning = Color.Unspecified,
textFieldFrame = Color.Unspecified, textFieldFrame = Color.Unspecified,
textDescription = Color.Unspecified, textDescription = Color.Unspecified,
textPending = Color.Unspecified, textPending = Color.Unspecified,
@ -289,5 +298,6 @@ internal val LocalExtendedColors =
radioButtonTextColor = Color.Unspecified, radioButtonTextColor = Color.Unspecified,
historyBackgroundColor = Color.Unspecified, historyBackgroundColor = Color.Unspecified,
historyRedColor = Color.Unspecified, historyRedColor = Color.Unspecified,
historySyncingColor = Color.Unspecified,
) )
} }

View File

@ -6,6 +6,7 @@ import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.BalanceStateFixture import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.history.fixture.TransactionHistoryUiStateFixture import co.electriccoin.zcash.ui.screen.account.history.fixture.TransactionHistoryUiStateFixture
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState 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.Account
@ -70,6 +71,7 @@ class AccountTestSetup(
onItemClickCount.incrementAndGet() onItemClickCount.incrementAndGet()
}, },
walletRestoringState = WalletRestoringState.NONE, walletRestoringState = WalletRestoringState.NONE,
walletSnapshot = WalletSnapshotFixture.new()
) )
} }

View File

@ -3,6 +3,7 @@ package co.electriccoin.zcash.ui.screen.account.history
import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
import co.electriccoin.zcash.ui.screen.account.view.HistoryContainer import co.electriccoin.zcash.ui.screen.account.view.HistoryContainer
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -32,7 +33,8 @@ class HistoryTestSetup(
onTransactionItemAction = { onTransactionItemAction = {
onItemIdClickCount.incrementAndGet() onItemIdClickCount.incrementAndGet()
}, },
walletRestoringState = WalletRestoringState.NONE walletRestoringState = WalletRestoringState.NONE,
walletSnapshot = WalletSnapshotFixture.new()
) )
} }
} }

View File

@ -0,0 +1,93 @@
package co.electriccoin.zcash.ui.common.compose
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.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Devices
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.BodySmall
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.LinearProgressIndicator
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.balances.model.WalletDisplayValues
@Preview(device = Devices.PIXEL_4_XL)
@Composable
private fun BalanceWidgetPreview() {
ZcashTheme(forceDarkMode = false) {
GradientSurface(
modifier = Modifier.fillMaxWidth()
) {
SynchronizationStatus(
isUpdateAvailable = false,
isDetailedStatus = false,
walletSnapshot = WalletSnapshotFixture.new()
)
}
}
}
@Composable
fun SynchronizationStatus(
isUpdateAvailable: Boolean,
isDetailedStatus: Boolean,
walletSnapshot: WalletSnapshot,
modifier: Modifier = Modifier,
testTag: String? = null,
) {
val walletDisplayValues =
WalletDisplayValues.getNextValues(
context = LocalContext.current,
walletSnapshot = walletSnapshot,
isUpdateAvailable = isUpdateAvailable,
isDetailedStatus = isDetailedStatus
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
if (walletDisplayValues.statusText.isNotEmpty()) {
BodySmall(
text = walletDisplayValues.statusText,
modifier = testTag?.let { Modifier.testTag(testTag) } ?: Modifier,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
}
BodySmall(
text =
stringResource(
id = R.string.balances_status_syncing_percentage,
walletSnapshot.progress.toPercentageWithDecimal()
),
textFontWeight = FontWeight.Black
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
LinearProgressIndicator(
progress = walletSnapshot.progress.decimal,
modifier =
Modifier.padding(
horizontal = ZcashTheme.dimens.spacingUpLarge
)
)
}
}

View File

@ -14,6 +14,7 @@ import co.electriccoin.zcash.spackle.ClipboardManagerUtil
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.BalanceState import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
@ -48,6 +49,8 @@ internal fun WrapAccount(
val balanceState = walletViewModel.balanceState.collectAsStateWithLifecycle().value val balanceState = walletViewModel.balanceState.collectAsStateWithLifecycle().value
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
WrapAccount( WrapAccount(
balanceState = balanceState, balanceState = balanceState,
context = activity.applicationContext, context = activity.applicationContext,
@ -57,7 +60,8 @@ internal fun WrapAccount(
synchronizer = synchronizer, synchronizer = synchronizer,
transactionHistoryViewModel = transactionHistoryViewModel, transactionHistoryViewModel = transactionHistoryViewModel,
transactionsUiState = transactionsUiState, transactionsUiState = transactionsUiState,
walletRestoringState = walletRestoringState walletRestoringState = walletRestoringState,
walletSnapshot = walletSnapshot
) )
// For benchmarking purposes // For benchmarking purposes
@ -77,8 +81,9 @@ internal fun WrapAccount(
synchronizer: Synchronizer?, synchronizer: Synchronizer?,
transactionHistoryViewModel: TransactionHistoryViewModel, transactionHistoryViewModel: TransactionHistoryViewModel,
walletRestoringState: WalletRestoringState, walletRestoringState: WalletRestoringState,
walletSnapshot: WalletSnapshot?
) { ) {
if (null == synchronizer) { if (null == synchronizer || null == walletSnapshot) {
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer // 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 // TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
// TODO [#1146]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146 // TODO [#1146]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146
@ -127,7 +132,8 @@ internal fun WrapAccount(
}, },
goBalances = goBalances, goBalances = goBalances,
goSettings = goSettings, goSettings = goSettings,
walletRestoringState = walletRestoringState walletRestoringState = walletRestoringState,
walletSnapshot = walletSnapshot
) )
} }
} }

View File

@ -20,11 +20,13 @@ import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.BalanceState import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.compose.BalanceWidget import co.electriccoin.zcash.ui.common.compose.BalanceWidget
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.test.CommonTag import co.electriccoin.zcash.ui.common.test.CommonTag
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.BalanceStateFixture import co.electriccoin.zcash.ui.fixture.BalanceStateFixture
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.AccountTag import co.electriccoin.zcash.ui.screen.account.AccountTag
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
@ -40,7 +42,8 @@ private fun HistoryLoadingComposablePreview() {
onTransactionItemAction = {}, onTransactionItemAction = {},
transactionsUiState = TransactionUiState.Loading, transactionsUiState = TransactionUiState.Loading,
walletRestoringState = WalletRestoringState.SYNCING, walletRestoringState = WalletRestoringState.SYNCING,
balanceState = BalanceStateFixture.new() balanceState = BalanceStateFixture.new(),
walletSnapshot = WalletSnapshotFixture.new(),
) )
} }
} }
@ -55,10 +58,11 @@ private fun HistoryListComposablePreview() {
Account( Account(
goBalances = {}, goBalances = {},
goSettings = {}, goSettings = {},
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)),
onTransactionItemAction = {}, onTransactionItemAction = {},
transactionsUiState = TransactionUiState.Done(transactions = TransactionsFixture.new()), transactionsUiState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
walletRestoringState = WalletRestoringState.NONE, walletRestoringState = WalletRestoringState.NONE,
balanceState = BalanceState.Available(Zatoshi(123_000_000L), Zatoshi(123_000_000L)) walletSnapshot = WalletSnapshotFixture.new(),
) )
} }
} }
@ -73,6 +77,7 @@ internal fun Account(
onTransactionItemAction: (TrxItemAction) -> Unit, onTransactionItemAction: (TrxItemAction) -> Unit,
transactionsUiState: TransactionUiState, transactionsUiState: TransactionUiState,
walletRestoringState: WalletRestoringState, walletRestoringState: WalletRestoringState,
walletSnapshot: WalletSnapshot,
) { ) {
Scaffold(topBar = { Scaffold(topBar = {
AccountTopAppBar( AccountTopAppBar(
@ -83,9 +88,10 @@ internal fun Account(
AccountMainContent( AccountMainContent(
balanceState = balanceState, balanceState = balanceState,
goBalances = goBalances, goBalances = goBalances,
onTransactionItemAction = onTransactionItemAction,
transactionState = transactionsUiState, transactionState = transactionsUiState,
walletRestoringState = walletRestoringState, walletRestoringState = walletRestoringState,
onTransactionItemAction = onTransactionItemAction, walletSnapshot = walletSnapshot,
modifier = modifier =
Modifier.padding( Modifier.padding(
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault, top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
@ -131,6 +137,7 @@ private fun AccountMainContent(
onTransactionItemAction: (TrxItemAction) -> Unit, onTransactionItemAction: (TrxItemAction) -> Unit,
transactionState: TransactionUiState, transactionState: TransactionUiState,
walletRestoringState: WalletRestoringState, walletRestoringState: WalletRestoringState,
walletSnapshot: WalletSnapshot,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column( Column(
@ -150,9 +157,10 @@ private fun AccountMainContent(
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge)) Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingUpLarge))
HistoryContainer( HistoryContainer(
onTransactionItemAction = onTransactionItemAction,
transactionState = transactionState, transactionState = transactionState,
walletRestoringState = walletRestoringState, walletRestoringState = walletRestoringState,
onTransactionItemAction = onTransactionItemAction, walletSnapshot = walletSnapshot,
) )
} }
} }

View File

@ -45,18 +45,22 @@ import cash.z.ecc.android.sdk.model.TransactionState
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.toZecString import cash.z.ecc.android.sdk.model.toZecString
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.design.component.CircularMidProgressIndicator import co.electriccoin.zcash.ui.design.component.CircularMidProgressIndicator
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.StyledBalance import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.component.TextWithIcon import co.electriccoin.zcash.ui.design.component.TextWithIcon
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.HistoryTag import co.electriccoin.zcash.ui.screen.account.HistoryTag
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionUiFixture import co.electriccoin.zcash.ui.screen.account.fixture.TransactionUiFixture
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi 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.TransactionUiState
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
import co.electriccoin.zcash.ui.screen.balances.BalancesTag
import co.electriccoin.zcash.ui.screen.send.view.DEFAULT_LESS_THAN_FEE import co.electriccoin.zcash.ui.screen.send.view.DEFAULT_LESS_THAN_FEE
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
@ -70,9 +74,10 @@ private fun ComposablePreview() {
ZcashTheme(forceDarkMode = false) { ZcashTheme(forceDarkMode = false) {
GradientSurface { GradientSurface {
HistoryContainer( HistoryContainer(
transactionState = TransactionUiState.Loading,
onTransactionItemAction = {}, onTransactionItemAction = {},
walletRestoringState = WalletRestoringState.SYNCING transactionState = TransactionUiState.Loading,
walletRestoringState = WalletRestoringState.SYNCING,
walletSnapshot = WalletSnapshotFixture.new()
) )
} }
} }
@ -86,7 +91,8 @@ private fun ComposableHistoryListPreview() {
HistoryContainer( HistoryContainer(
transactionState = TransactionUiState.Done(transactions = TransactionsFixture.new()), transactionState = TransactionUiState.Done(transactions = TransactionsFixture.new()),
onTransactionItemAction = {}, onTransactionItemAction = {},
walletRestoringState = WalletRestoringState.NONE walletRestoringState = WalletRestoringState.RESTORING,
walletSnapshot = WalletSnapshotFixture.new()
) )
} }
} }
@ -103,12 +109,13 @@ private val dateFormat: DateFormat by lazy {
@Composable @Composable
internal fun HistoryContainer( internal fun HistoryContainer(
transactionState: TransactionUiState,
onTransactionItemAction: (TrxItemAction) -> Unit, onTransactionItemAction: (TrxItemAction) -> Unit,
transactionState: TransactionUiState,
walletRestoringState: WalletRestoringState, walletRestoringState: WalletRestoringState,
walletSnapshot: WalletSnapshot,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Box( Column(
modifier = modifier =
modifier modifier
.then( .then(
@ -117,6 +124,24 @@ internal fun HistoryContainer(
.background(ZcashTheme.colors.historyBackgroundColor) .background(ZcashTheme.colors.historyBackgroundColor)
) )
) { ) {
if (walletRestoringState == WalletRestoringState.RESTORING) {
Column(
modifier = Modifier.background(color = ZcashTheme.colors.historySyncingColor)
) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
// Do not show the app update information and the detailed sync status in the restoring status
// on Account screen
SynchronizationStatus(
isDetailedStatus = false,
isUpdateAvailable = false,
testTag = BalancesTag.STATUS,
walletSnapshot = walletSnapshot,
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
}
}
when (transactionState) { when (transactionState) {
TransactionUiState.Loading -> { TransactionUiState.Loading -> {
LoadingTransactionHistory() LoadingTransactionHistory()

View File

@ -86,7 +86,7 @@ data class WalletDisplayValues(
} }
Synchronizer.Status.STOPPED -> { Synchronizer.Status.STOPPED -> {
if (isDetailedStatus) { if (isDetailedStatus) {
statusText = context.getString(R.string.balances_status_stopped) statusText = context.getString(R.string.balances_status_detailed_stopped)
} else { } else {
statusText = context.getString(R.string.balances_status_syncing) statusText = context.getString(R.string.balances_status_syncing)
} }

View File

@ -50,10 +50,10 @@ import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.toZecString import cash.z.ecc.android.sdk.model.toZecString
import cash.z.ecc.sdk.extension.toPercentageWithDecimal
import co.electriccoin.zcash.ui.R import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.BalanceState import co.electriccoin.zcash.ui.common.compose.BalanceState
import co.electriccoin.zcash.ui.common.compose.BalanceWidget import co.electriccoin.zcash.ui.common.compose.BalanceWidget
import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus
import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletRestoringState
import co.electriccoin.zcash.ui.common.model.WalletSnapshot import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.changePendingBalance import co.electriccoin.zcash.ui.common.model.changePendingBalance
@ -67,9 +67,9 @@ import co.electriccoin.zcash.ui.design.component.BodyWithFiatCurrencySymbol
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.design.component.CircularSmallProgressIndicator import co.electriccoin.zcash.ui.design.component.CircularSmallProgressIndicator
import co.electriccoin.zcash.ui.design.component.GradientSurface import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.LinearProgressIndicator
import co.electriccoin.zcash.ui.design.component.PrimaryButton import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.Reference import co.electriccoin.zcash.ui.design.component.Reference
import co.electriccoin.zcash.ui.design.component.Small
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.component.StyledBalance import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@ -162,6 +162,7 @@ fun Balances(
start = ZcashTheme.dimens.screenHorizontalSpacingRegular, start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
end = ZcashTheme.dimens.screenHorizontalSpacingRegular end = ZcashTheme.dimens.screenHorizontalSpacingRegular
), ),
walletRestoringState = walletRestoringState
) )
// Show shielding error popup // Show shielding error popup
@ -245,6 +246,7 @@ private fun BalancesMainContent(
onShielding: () -> Unit, onShielding: () -> Unit,
walletSnapshot: WalletSnapshot, walletSnapshot: WalletSnapshot,
shieldState: ShieldState, shieldState: ShieldState,
walletRestoringState: WalletRestoringState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column( Column(
@ -285,14 +287,30 @@ private fun BalancesMainContent(
walletSnapshot = walletSnapshot, walletSnapshot = walletSnapshot,
) )
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Spacer(modifier = Modifier.weight(1f, true)) Spacer(modifier = Modifier.weight(1f, true))
SyncStatus( Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
if (walletRestoringState == WalletRestoringState.RESTORING) {
Small(
text = stringResource(id = R.string.balances_status_restoring_text),
textFontWeight = FontWeight.Medium,
color = ZcashTheme.colors.textFieldWarning,
textAlign = TextAlign.Center,
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = ZcashTheme.dimens.spacingDefault)
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
}
SynchronizationStatus(
walletSnapshot = walletSnapshot, walletSnapshot = walletSnapshot,
isUpdateAvailable = isUpdateAvailable, isUpdateAvailable = isUpdateAvailable,
isDetailedStatus = isDetailedStatus, isDetailedStatus = isDetailedStatus,
testTag = BalancesTag.STATUS
) )
} }
} }
@ -609,51 +627,3 @@ fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
} }
} }
} }
@Composable
fun SyncStatus(
isUpdateAvailable: Boolean,
isDetailedStatus: Boolean,
walletSnapshot: WalletSnapshot,
) {
val walletDisplayValues =
WalletDisplayValues.getNextValues(
context = LocalContext.current,
walletSnapshot = walletSnapshot,
isUpdateAvailable = isUpdateAvailable,
isDetailedStatus = isDetailedStatus
)
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
if (walletDisplayValues.statusText.isNotEmpty()) {
BodySmall(
text = walletDisplayValues.statusText,
modifier = Modifier.testTag(BalancesTag.STATUS),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
}
BodySmall(
text =
stringResource(
id = R.string.balances_status_syncing_percentage,
walletSnapshot.progress.toPercentageWithDecimal()
),
textFontWeight = FontWeight.Black
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
LinearProgressIndicator(
progress = walletSnapshot.progress.decimal,
modifier =
Modifier.padding(
horizontal = ZcashTheme.dimens.spacingUpLarge
)
)
}
}

View File

@ -23,7 +23,8 @@
<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" 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_connection">Disconnected</string>
<string name="balances_status_error_detailed_unknown">Unknown cause</string> <string name="balances_status_error_detailed_unknown">Unknown cause</string>
<string name="balances_status_stopped">Synchronizer stopped</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_dialog_error_title">Failed to shield funds</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_text">Error: The attempt to shield the transparent funds failed. Try it again, please.</string>

View File

@ -3,4 +3,8 @@
<string name="home_tab_send">Send</string> <string name="home_tab_send">Send</string>
<string name="home_tab_receive">Receive</string> <string name="home_tab_receive">Receive</string>
<string name="home_tab_balances">Balances</string> <string name="home_tab_balances">Balances</string>
<string name="restoring_initial_dialog_title">Success</string>
<string name="restoring_initial_dialog_description">Your wallet has been successfully restored! During the initial sync, your funds cannot be spent or sent. Depending on the age of your wallet, it may take a few hours to fully sync.</string>
<string name="restoring_initial_dialog_positive_button">OK</string>
</resources> </resources>