[#1219] Current balances UI

* [#1219] Current balances UI

- This represents UI changes for balance, change, and transaction on the Balances screen
- Reworked StyledBalance to be more reusable, too
- Adopted the latest SDK changes related to change pending and pending transactions
- Closes #1224
- Closes #1219

* Adopted latest SDK snapshot version
This commit is contained in:
Honza Rychnovský 2024-02-07 18:28:26 +01:00 committed by GitHub
parent c5efcabf4c
commit 1058802b19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 354 additions and 133 deletions

View File

@ -188,7 +188,7 @@ ZCASH_BIP39_VERSION=1.0.7
ZXING_VERSION=3.5.2
# WARNING: Ensure a non-snapshot version is used before releasing to production.
ZCASH_SDK_VERSION=2.0.6
ZCASH_SDK_VERSION=2.0.6-SNAPSHOT
# Toolchain is the Java version used to build the application, which is separate from the
# Java version used to run the application.

View File

@ -1,13 +1,15 @@
package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@Preview
@ -15,7 +17,10 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
private fun CircularScreenProgressIndicatorComposablePreview() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
CircularScreenProgressIndicator()
Column {
CircularScreenProgressIndicator()
CircularSmallProgressIndicator()
}
}
}
}
@ -30,7 +35,22 @@ fun CircularScreenProgressIndicator(modifier: Modifier = Modifier) {
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.width(ZcashTheme.dimens.circularScreenProgressWidth)
color = ZcashTheme.colors.progressBarScreen,
modifier =
Modifier
.size(ZcashTheme.dimens.circularScreenProgressWidth)
)
}
}
@Composable
fun CircularSmallProgressIndicator(modifier: Modifier = Modifier) {
CircularProgressIndicator(
color = ZcashTheme.colors.progressBarSmall,
strokeWidth = 2.dp,
modifier =
Modifier
.size(ZcashTheme.dimens.circularSmallProgressWidth)
.then(modifier)
)
}

View File

@ -30,7 +30,10 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.model.MonetarySeparators
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import java.util.Locale
@Preview
@Composable
@ -67,12 +70,11 @@ private fun StyledBalanceComposablePreview() {
GradientSurface {
Column {
StyledBalance(
mainPart = "1,234.567",
secondPart = "89012",
balanceString = "1,234.56789012",
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceStyles.first,
ZcashTheme.extendedTypography.balanceStyles.second
ZcashTheme.extendedTypography.balanceWidgetStyles.first,
ZcashTheme.extendedTypography.balanceWidgetStyles.second
),
modifier = Modifier
)
@ -222,19 +224,6 @@ fun Tiny(
)
}
@Composable
fun ListItem(
text: String,
modifier: Modifier = Modifier
) {
Text(
text = text,
style = ZcashTheme.extendedTypography.listItem,
color = MaterialTheme.colorScheme.onBackground,
modifier = modifier
)
}
@Composable
fun ListHeader(
text: String,
@ -293,44 +282,78 @@ fun Reference(
}
/**
* Pass amount of Zcash tokens you want to display and the component style it according to the design requirements.
* This accepts string with balance and displays it in the UI component styled according to the design
* requirements. The function displays the balance within two parts.
*
* @param mainPart of Zcash tokens to be displayed in a bigger font style
* @param secondPart of Zcash tokens to be displayed in a smaller font style
* @param modifier to modify the Text UI element as needed
* @param balanceString String of Zcash formatted balance
* @param textStyles Styles for the first and second part of the balance
* @param textColor Optional color to modify the default font color from [textStyles]
* @param modifier Modifier to modify the Text UI element as needed
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun StyledBalance(
mainPart: String,
secondPart: String,
balanceString: String,
textStyles: Pair<TextStyle, TextStyle>,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
textColor: Color? = null
) {
val balanceSplit = splitBalance(balanceString)
val content =
buildAnnotatedString {
withStyle(
style = textStyles.first.toSpanStyle()
) {
append(mainPart)
append(balanceSplit.first)
}
withStyle(
style = textStyles.second.toSpanStyle()
) {
append(secondPart)
append(balanceSplit.second)
}
}
Text(
text = content,
// fixme color
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
modifier =
Modifier
.basicMarquee()
.then(modifier)
)
if (textColor != null) {
Text(
text = content,
color = textColor,
maxLines = 1,
modifier =
Modifier
.basicMarquee()
.then(modifier)
)
} else {
Text(
text = content,
maxLines = 1,
modifier =
Modifier
.basicMarquee()
.then(modifier)
)
}
}
private fun splitBalance(balance: String): Pair<String, String> {
Twig.debug { "Balance before split: $balance" }
@Suppress("MAGIC_CONSTANT", "MagicNumber")
val cutPosition = balance.indexOf(MonetarySeparators.current(Locale.US).decimal) + 4
val firstPart =
balance.substring(
startIndex = 0,
endIndex = cutPosition
)
val secondPart =
balance.substring(
startIndex = cutPosition
)
Twig.debug { "Balance after split: $firstPart|$secondPart" }
return Pair(firstPart, secondPart)
}
@Composable

View File

@ -30,6 +30,7 @@ data class Dimens(
val chipStroke: Dp,
// Progress
val circularScreenProgressWidth: Dp,
val circularSmallProgressWidth: Dp,
// TopAppBar:
val topAppBarZcashLogoHeight: Dp,
val topAppBarActionRippleCorner: Dp,
@ -37,6 +38,7 @@ data class Dimens(
val textFieldDefaultHeight: Dp,
val textFieldPanelDefaultHeight: Dp,
// Any Layout:
val divider: Dp,
val layoutStroke: Dp,
// Screen custom spacings:
val inScreenZcashLogoHeight: Dp,
@ -64,11 +66,13 @@ private val defaultDimens =
chipShadowElevation = 4.dp,
chipStroke = 0.5.dp,
circularScreenProgressWidth = 48.dp,
circularSmallProgressWidth = 14.dp,
topAppBarZcashLogoHeight = 24.dp,
topAppBarActionRippleCorner = 28.dp,
textFieldDefaultHeight = 64.dp,
textFieldPanelDefaultHeight = 215.dp,
layoutStroke = 1.dp,
divider = 1.dp,
inScreenZcashLogoHeight = 100.dp,
inScreenZcashLogoWidth = 60.dp,
inScreenZcashTextLogoHeight = 30.dp,

View File

@ -14,12 +14,13 @@ data class ExtendedColors(
val onTertiary: Color,
val callout: Color,
val onCallout: Color,
val progressStart: Color,
val progressEnd: Color,
val progressBackground: Color,
val progressBarSmall: Color,
val progressBarScreen: Color,
val chipIndex: Color,
val textCommon: Color,
val textFieldHint: Color,
val textDescription: Color,
val textPending: Color,
val layoutStroke: Color,
val overlay: Color,
val highlight: Color,
@ -34,6 +35,7 @@ data class ExtendedColors(
val welcomeAnimationColor: Color,
val complementaryColor: Color,
val dividerColor: Color,
val darkDividerColor: Color,
val tabTextColor: Color,
) {
@Composable

View File

@ -24,10 +24,11 @@ internal object Dark {
val textSecondaryButton = Color(0xFF000000)
val textTertiaryButton = Color.White
val textNavigationButton = Color.Black
val textCaption = Color(0xFFFFFFFF)
val textCommon = Color(0xFFFFFFFF)
val textChipIndex = Color(0xFFFFB900)
val textFieldHint = Color(0xFFB7B7B7)
val textDescription = Color(0xFF777777)
val textProgress = Color(0xFF8B8A8A)
val layoutStroke = Color(0xFFFFFFFF)
@ -45,9 +46,8 @@ internal object Dark {
val navigationButton = Color(0xFFFFFFFF)
val navigationButtonPressed = Color(0xFFFFFFFF)
val progressStart = Color(0xFFF364CE)
val progressEnd = Color(0xFFF8964F)
val progressBackground = Color(0xFF929bb3)
val progressBarSmall = Color(0xFF8B8A8A)
val progressBarScreen = Color(0xFFFFFFFF)
val callout = Color(0xFFFFFFFF)
val onCallout = Color(0xFFFFFFFF)
@ -55,11 +55,6 @@ internal object Dark {
val overlay = Color(0x22000000)
val highlight = Color(0xFFFFD800)
val addressHighlightBorder = Color(0xFF525252)
val addressHighlightUnified = Color(0xFFFFD800)
val addressHighlightSapling = Color(0xFF1BBFF6)
val addressHighlightTransparent = Color(0xFF97999A)
val dangerous = Color(0xFFEC0008)
val onDangerous = Color(0xFFFFFFFF)
@ -76,6 +71,7 @@ internal object Dark {
val welcomeAnimationColor = Color(0xFF231F20)
val complementaryColor = Color(0xFFF4B728)
val dividerColor = Color(0xFFDDDDDD)
val darkDividerColor = Color(0xFF000000)
val tabTextColor = Color(0xFF040404)
}
@ -89,10 +85,11 @@ internal object Light {
val textPrimaryButton = Color(0xFFFFFFFF)
val textSecondaryButton = Color(0xFF000000)
val textTertiaryButton = Color(0xFF000000)
val textCaption = Color(0xFF000000)
val textCommon = Color(0xFF000000)
val textChipIndex = Color(0xFFEE8592)
val textFieldHint = Color(0xFFB7B7B7)
val textDescription = Color(0xFF777777)
val textProgress = Color(0xFF8B8A8A)
val layoutStroke = Color(0xFF000000)
@ -110,9 +107,8 @@ internal object Light {
val navigationButton = Color(0xFFFFFFFF)
val navigationButtonPressed = Color(0xFFFFFFFF)
val progressStart = Color(0xFFF364CE)
val progressEnd = Color(0xFFF8964F)
val progressBackground = Color(0xFFFFFFFF)
val progressBarSmall = Color(0xFF8B8A8A)
val progressBarScreen = Color(0xFF000000)
val callout = Color(0xFFFFFFFF)
val onCallout = Color(0xFFFFFFFF)
@ -134,6 +130,7 @@ internal object Light {
val welcomeAnimationColor = Color(0xFF231F20)
val complementaryColor = Color(0xFFF4B728)
val dividerColor = Color(0xFFDDDDDD)
val darkDividerColor = Color(0xFF000000)
val tabTextColor = Color(0xFF040404)
}
@ -169,12 +166,13 @@ internal val DarkExtendedColorPalette =
onTertiary = Dark.textTertiaryButton,
callout = Dark.callout,
onCallout = Dark.onCallout,
progressStart = Dark.progressStart,
progressEnd = Dark.progressEnd,
progressBackground = Dark.progressBackground,
progressBarSmall = Dark.progressBarSmall,
progressBarScreen = Dark.progressBarScreen,
chipIndex = Dark.textChipIndex,
textCommon = Dark.textCommon,
textFieldHint = Dark.textFieldHint,
textDescription = Dark.textDescription,
textPending = Dark.textProgress,
layoutStroke = Dark.layoutStroke,
overlay = Dark.overlay,
highlight = Dark.highlight,
@ -189,6 +187,7 @@ internal val DarkExtendedColorPalette =
welcomeAnimationColor = Dark.welcomeAnimationColor,
complementaryColor = Dark.complementaryColor,
dividerColor = Dark.dividerColor,
darkDividerColor = Dark.darkDividerColor,
tabTextColor = Dark.tabTextColor,
)
@ -200,12 +199,13 @@ internal val LightExtendedColorPalette =
onTertiary = Light.textTertiaryButton,
callout = Light.callout,
onCallout = Light.onCallout,
progressStart = Light.progressStart,
progressEnd = Light.progressEnd,
progressBackground = Light.progressBackground,
progressBarScreen = Light.progressBarScreen,
progressBarSmall = Light.progressBarSmall,
chipIndex = Light.textChipIndex,
textCommon = Light.textCommon,
textFieldHint = Light.textFieldHint,
textDescription = Light.textDescription,
textPending = Light.textProgress,
layoutStroke = Light.layoutStroke,
overlay = Light.overlay,
highlight = Light.highlight,
@ -220,7 +220,8 @@ internal val LightExtendedColorPalette =
welcomeAnimationColor = Light.welcomeAnimationColor,
complementaryColor = Light.complementaryColor,
dividerColor = Light.dividerColor,
tabTextColor = Dark.tabTextColor,
darkDividerColor = Light.darkDividerColor,
tabTextColor = Light.tabTextColor,
)
@Suppress("CompositionLocalAllowlist")
@ -233,12 +234,13 @@ internal val LocalExtendedColors =
onTertiary = Color.Unspecified,
callout = Color.Unspecified,
onCallout = Color.Unspecified,
progressStart = Color.Unspecified,
progressEnd = Color.Unspecified,
progressBackground = Color.Unspecified,
progressBarScreen = Color.Unspecified,
progressBarSmall = Color.Unspecified,
chipIndex = Color.Unspecified,
textCommon = Color.Unspecified,
textFieldHint = Color.Unspecified,
textDescription = Color.Unspecified,
textPending = Color.Unspecified,
layoutStroke = Color.Unspecified,
overlay = Color.Unspecified,
highlight = Color.Unspecified,
@ -253,6 +255,7 @@ internal val LocalExtendedColors =
welcomeAnimationColor = Color.Unspecified,
complementaryColor = Color.Unspecified,
dividerColor = Color.Unspecified,
darkDividerColor = Color.Unspecified,
tabTextColor = Color.Unspecified
)
}

View File

@ -73,8 +73,8 @@ internal val PrimaryTypography =
bodySmall =
TextStyle(
fontFamily = InterFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 16.sp
fontWeight = FontWeight.Normal,
fontSize = 14.sp
),
labelLarge =
TextStyle(
@ -139,18 +139,26 @@ data class Typography(
)
@Immutable
data class BalanceTextStyles(
data class BalanceWidgetTextStyles(
val first: TextStyle,
val second: TextStyle,
val third: TextStyle,
val fourth: TextStyle,
)
@Immutable
data class BalanceSingleTextStyles(
val first: TextStyle,
val second: TextStyle,
)
@Immutable
data class ExtendedTypography(
val listItem: TextStyle,
// Grouping balances text styles to a wrapper class
val balanceStyles: BalanceTextStyles,
// Grouping balances text styles to a wrapper class for BalanceWidget
val balanceWidgetStyles: BalanceWidgetTextStyles,
// Grouping balances text styles to a wrapper class for single balance use case
val balanceSingleStyles: BalanceSingleTextStyles,
val addressStyle: TextStyle,
val aboutText: TextStyle,
val buttonText: TextStyle,
@ -180,8 +188,8 @@ val LocalExtendedTypography =
fontSize = 24.sp
),
// Note: the order here matters, be careful when reordering
balanceStyles =
BalanceTextStyles(
balanceWidgetStyles =
BalanceWidgetTextStyles(
first =
SecondaryTypography.headlineLarge.copy(
fontSize = 42.sp,
@ -205,6 +213,19 @@ val LocalExtendedTypography =
fontWeight = FontWeight.Bold
)
),
balanceSingleStyles =
BalanceSingleTextStyles(
first =
SecondaryTypography.bodySmall.copy(
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
),
second =
SecondaryTypography.bodySmall.copy(
fontSize = 8.sp,
fontWeight = FontWeight.SemiBold
)
),
addressStyle =
SecondaryTypography.bodyLarge.copy(
// TODO [#1032]: Addresses can be shown with "×" symbols

View File

@ -71,7 +71,7 @@ internal class MockSynchronizer : CloseableSynchronizer {
override val transactions: Flow<List<TransactionOverview>>
get() = TODO("Not yet implemented")
override val transparentBalances: StateFlow<WalletBalance?>
override val transparentBalance: StateFlow<Zatoshi?>
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
override fun close() {
@ -98,7 +98,7 @@ internal class MockSynchronizer : CloseableSynchronizer {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
override suspend fun getTransparentBalance(tAddr: String): Zatoshi {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}

View File

@ -6,7 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.fixture.WalletBalanceFixture
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZecSend
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@ -101,8 +101,7 @@ class SendViewTestSetup(
walletSnapshot =
WalletSnapshotFixture.new(
saplingBalance =
WalletBalance(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
WalletBalanceFixture.new(
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100))
)
),

View File

@ -6,8 +6,8 @@ import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.fixture.WalletBalanceFixture
import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.fixture.ZecSendFixture
@ -44,8 +44,7 @@ class SendViewIntegrationTest {
private val walletSnapshot =
WalletSnapshotFixture.new(
saplingBalance =
WalletBalance(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
WalletBalanceFixture.new(
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100))
)
)

View File

@ -17,12 +17,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.toZecString
import cash.z.ecc.sdk.type.ZcashCurrency
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.totalBalance
@ -33,7 +31,6 @@ import co.electriccoin.zcash.ui.design.component.Reference
import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import java.util.Locale
@Preview(device = Devices.PIXEL_2)
@Composable
@ -49,7 +46,8 @@ private fun BalanceWidgetPreview() {
saplingBalance =
WalletBalance(
Zatoshi(1234567891234567),
Zatoshi(123456789)
Zatoshi(123456789),
Zatoshi(123)
)
),
isReferenceToBalances = true,
@ -60,26 +58,6 @@ private fun BalanceWidgetPreview() {
}
}
fun splitBalance(balance: String): Pair<String, String> {
Twig.debug { "Balance before split: $balance" }
@Suppress("MAGIC_CONSTANT", "MagicNumber")
val cutPosition = balance.indexOf(MonetarySeparators.current(Locale.US).decimal) + 4
val firstPart =
balance.substring(
startIndex = 0,
endIndex = cutPosition
)
val secondPart =
balance.substring(
startIndex = cutPosition
)
Twig.debug { "Balance after split: $firstPart|$secondPart" }
return Pair(firstPart, secondPart)
}
@Composable
@Suppress("LongMethod")
fun BalanceWidget(
@ -95,18 +73,15 @@ fun BalanceWidget(
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
val totalBalanceSplit = splitBalance(walletSnapshot.totalBalance().toZecString())
Row(
verticalAlignment = Alignment.CenterVertically
) {
StyledBalance(
mainPart = totalBalanceSplit.first,
secondPart = totalBalanceSplit.second,
balanceString = walletSnapshot.totalBalance().toZecString(),
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceStyles.first,
ZcashTheme.extendedTypography.balanceStyles.second
ZcashTheme.extendedTypography.balanceWidgetStyles.first,
ZcashTheme.extendedTypography.balanceWidgetStyles.second
)
)
@ -136,15 +111,12 @@ fun BalanceWidget(
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
val availableBalanceSplit = splitBalance(walletSnapshot.spendableBalance().toZecString())
StyledBalance(
mainPart = availableBalanceSplit.first,
secondPart = availableBalanceSplit.second,
balanceString = walletSnapshot.spendableBalance().toZecString(),
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceStyles.third,
ZcashTheme.extendedTypography.balanceStyles.fourth
ZcashTheme.extendedTypography.balanceWidgetStyles.third,
ZcashTheme.extendedTypography.balanceWidgetStyles.fourth
)
)

View File

@ -15,7 +15,7 @@ data class WalletSnapshot(
val processorInfo: CompactBlockProcessor.ProcessorInfo,
val orchardBalance: WalletBalance,
val saplingBalance: WalletBalance,
val transparentBalance: WalletBalance,
val transparentBalance: Zatoshi,
val progress: PercentDecimal,
val synchronizerError: SynchronizerError?
) {
@ -28,9 +28,15 @@ data class WalletSnapshot(
val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds
}
fun WalletSnapshot.totalBalance() = orchardBalance.total + saplingBalance.total + transparentBalance.total
fun WalletSnapshot.totalBalance() = orchardBalance.total + saplingBalance.total + transparentBalance
// Note that considering both to be spendable is subject to change.
// The user experience could be confusing, and in the future we might prefer to ask users
// to transfer their balance to the latest balance type to make it spendable.
fun WalletSnapshot.spendableBalance() = orchardBalance.available + saplingBalance.available
// Note that summing both values could be confusing, and we might prefer dividing them in the future
fun WalletSnapshot.changePendingBalance() = orchardBalance.changePending + saplingBalance.changePending
// Note that summing both values could be confusing, and we might prefer dividing them in the future
fun WalletSnapshot.valuePendingBalance() = orchardBalance.valuePending + saplingBalance.valuePending

View File

@ -378,7 +378,7 @@ private fun Synchronizer.toWalletSnapshot() =
// 3
saplingBalances,
// 4
transparentBalances,
transparentBalance,
// 5
progress,
// 6
@ -386,16 +386,16 @@ private fun Synchronizer.toWalletSnapshot() =
) { flows ->
val orchardBalance = flows[2] as WalletBalance?
val saplingBalance = flows[3] as WalletBalance?
val transparentBalance = flows[4] as WalletBalance?
val transparentBalance = flows[4] as Zatoshi?
val progressPercentDecimal = flows[5] as PercentDecimal
WalletSnapshot(
flows[0] as Synchronizer.Status,
flows[1] as CompactBlockProcessor.ProcessorInfo,
orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)),
saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)),
transparentBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)),
orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
transparentBalance ?: Zatoshi(0),
progressPercentDecimal,
flows[6] as SynchronizerError?
)

View File

@ -2,9 +2,11 @@ package co.electriccoin.zcash.ui.fixture
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
import cash.z.ecc.android.sdk.fixture.WalletBalanceFixture
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
@ -12,9 +14,9 @@ import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
object WalletSnapshotFixture {
val STATUS = Synchronizer.Status.SYNCED
val PROGRESS = PercentDecimal.ZERO_PERCENT
val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1))
val ORCHARD_BALANCE: WalletBalance = WalletBalance(Zatoshi(5), Zatoshi(2))
val SAPLING_BALANCE: WalletBalance = WalletBalance(Zatoshi(4), Zatoshi(4))
val TRANSPARENT_BALANCE: Zatoshi = ZatoshiFixture.new(8)
val ORCHARD_BALANCE: WalletBalance = WalletBalanceFixture.newLong(8, 2, 1)
val SAPLING_BALANCE: WalletBalance = WalletBalanceFixture.newLong(5, 2, 1)
// Should fill in with non-empty values for better example values in tests and UI previews
@Suppress("LongParameterList")
@ -28,7 +30,7 @@ object WalletSnapshotFixture {
),
orchardBalance: WalletBalance = ORCHARD_BALANCE,
saplingBalance: WalletBalance = SAPLING_BALANCE,
transparentBalance: WalletBalance = TRANSPARENT_BALANCE,
transparentBalance: Zatoshi = TRANSPARENT_BALANCE,
progress: PercentDecimal = PROGRESS,
synchronizerError: SynchronizerError? = null
) = WalletSnapshot(

View File

@ -1,32 +1,49 @@
package co.electriccoin.zcash.ui.screen.balances.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.toZecString
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.BalanceWidget
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.changePendingBalance
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.valuePendingBalance
import co.electriccoin.zcash.ui.common.test.CommonTag
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.BodySmall
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.design.component.CircularSmallProgressIndicator
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.model.WalletDisplayValues
@ -106,7 +123,7 @@ private fun BalancesMainContent(
modifier =
Modifier
.fillMaxHeight()
// .verticalScroll(rememberScrollState()) Uncomment this once the whole screen UI is implemented
.verticalScroll(rememberScrollState())
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@ -118,20 +135,157 @@ private fun BalancesMainContent(
isReferenceToBalances = false,
onReferenceClick = {}
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
}
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
Divider(
color = ZcashTheme.colors.darkDividerColor,
thickness = ZcashTheme.dimens.divider
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
BalancesOverview(walletSnapshot)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
Body(
text = stringResource(id = R.string.balances_coming_soon),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
}
}
}
@Composable
fun BalancesOverview(walletSnapshot: WalletSnapshot) {
Column {
SpendableBalanceRow(walletSnapshot)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
ChangePendingRow(walletSnapshot)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
// aka value pending
PendingTransactionsRow(walletSnapshot)
}
}
const val TEXT_PART_WIDTH_RATIO = 0.6f
@Composable
fun SpendableBalanceRow(walletSnapshot: WalletSnapshot) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BodySmall(
text = stringResource(id = R.string.balances_shielded_spendable).uppercase(),
modifier = Modifier.fillMaxWidth(TEXT_PART_WIDTH_RATIO)
)
Row(verticalAlignment = Alignment.CenterVertically) {
StyledBalance(
balanceString = walletSnapshot.spendableBalance().toZecString(),
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceSingleStyles.first,
ZcashTheme.extendedTypography.balanceSingleStyles.second
),
textColor = ZcashTheme.colors.textCommon
)
Spacer(modifier = Modifier.width(12.dp))
Icon(
imageVector = ImageVector.vectorResource(R.drawable.balance_shield),
contentDescription = null,
// The same size as the following progress bars
modifier = Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)
)
}
}
}
@Composable
fun ChangePendingRow(walletSnapshot: WalletSnapshot) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BodySmall(
text = stringResource(id = R.string.balances_change_pending).uppercase(),
modifier = Modifier.fillMaxWidth(TEXT_PART_WIDTH_RATIO)
)
Row(verticalAlignment = Alignment.CenterVertically) {
val changePendingHasValue = walletSnapshot.changePendingBalance().value > 0L
StyledBalance(
balanceString = walletSnapshot.changePendingBalance().toZecString(),
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceSingleStyles.first,
ZcashTheme.extendedTypography.balanceSingleStyles.second
),
textColor = ZcashTheme.colors.textPending
)
Spacer(modifier = Modifier.width(12.dp))
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
if (changePendingHasValue) {
CircularSmallProgressIndicator()
}
}
}
}
}
@Composable
fun PendingTransactionsRow(walletSnapshot: WalletSnapshot) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BodySmall(
text = stringResource(id = R.string.balances_pending_transactions).uppercase(),
modifier = Modifier.fillMaxWidth(TEXT_PART_WIDTH_RATIO)
)
Row(verticalAlignment = Alignment.CenterVertically) {
val valuePendingHasValue = walletSnapshot.valuePendingBalance().value > 0L
StyledBalance(
balanceString = walletSnapshot.valuePendingBalance().toZecString(),
textStyles =
Pair(
ZcashTheme.extendedTypography.balanceSingleStyles.first,
ZcashTheme.extendedTypography.balanceSingleStyles.second
),
textColor = ZcashTheme.colors.textPending
)
Spacer(modifier = Modifier.width(12.dp))
Box(Modifier.width(ZcashTheme.dimens.circularSmallProgressWidth)) {
if (valuePendingHasValue) {
CircularSmallProgressIndicator()
}
}
}
}
}

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="11dp"
android:height="14dp"
android:viewportWidth="11"
android:viewportHeight="14">
<group>
<clip-path
android:pathData="M0,0h11v14h-11z"/>
<path
android:pathData="M10.727,1.675L5.61,0.032C5.537,0.004 5.463,0.004 5.39,0.032L0.272,1.675C0.04,1.748 0,1.804 0,2.067V8.092C0,8.875 0.297,9.677 0.881,10.476C1.328,11.086 1.946,11.698 2.717,12.295C4.014,13.3 5.291,13.916 5.344,13.941C5.448,13.993 5.552,13.993 5.657,13.941C5.71,13.916 6.986,13.3 8.284,12.295C9.055,11.698 9.673,11.085 10.12,10.476C10.704,9.677 11.001,8.875 11.001,8.092V2.067C10.988,1.794 10.915,1.756 10.729,1.675H10.727Z"
android:fillColor="#231F20"/>
</group>
</vector>

View File

@ -1,5 +1,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="balances_title">Balances</string>
<string name="balances_coming_soon">Coming soon:\n\nBalances overview,\nTransparent
<string name="balances_shielded_spendable">Shielded zec (spendable)</string>
<string name="balances_change_pending">Change pending</string>
<string name="balances_pending_transactions">Pending transactions</string>
<string name="balances_coming_soon">Coming soon:\n\nTransparent
funds shielding,\nBlock synchronization indicator</string>
</resources>