[#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 ZXING_VERSION=3.5.2
# WARNING: Ensure a non-snapshot version is used before releasing to production. # 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 # Toolchain is the Java version used to build the application, which is separate from the
# Java version used to run the application. # Java version used to run the application.

View File

@ -1,13 +1,15 @@
package co.electriccoin.zcash.ui.design.component package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize 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.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@Preview @Preview
@ -15,7 +17,10 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
private fun CircularScreenProgressIndicatorComposablePreview() { private fun CircularScreenProgressIndicatorComposablePreview() {
ZcashTheme(forceDarkMode = false) { ZcashTheme(forceDarkMode = false) {
GradientSurface { GradientSurface {
CircularScreenProgressIndicator() Column {
CircularScreenProgressIndicator()
CircularSmallProgressIndicator()
}
} }
} }
} }
@ -30,7 +35,22 @@ fun CircularScreenProgressIndicator(modifier: Modifier = Modifier) {
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
CircularProgressIndicator( 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.style.TextOverflow
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview 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 co.electriccoin.zcash.ui.design.theme.ZcashTheme
import java.util.Locale
@Preview @Preview
@Composable @Composable
@ -67,12 +70,11 @@ private fun StyledBalanceComposablePreview() {
GradientSurface { GradientSurface {
Column { Column {
StyledBalance( StyledBalance(
mainPart = "1,234.567", balanceString = "1,234.56789012",
secondPart = "89012",
textStyles = textStyles =
Pair( Pair(
ZcashTheme.extendedTypography.balanceStyles.first, ZcashTheme.extendedTypography.balanceWidgetStyles.first,
ZcashTheme.extendedTypography.balanceStyles.second ZcashTheme.extendedTypography.balanceWidgetStyles.second
), ),
modifier = Modifier 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 @Composable
fun ListHeader( fun ListHeader(
text: String, 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 balanceString String of Zcash formatted balance
* @param secondPart of Zcash tokens to be displayed in a smaller font style * @param textStyles Styles for the first and second part of the balance
* @param modifier to modify the Text UI element as needed * @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) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun StyledBalance( fun StyledBalance(
mainPart: String, balanceString: String,
secondPart: String,
textStyles: Pair<TextStyle, TextStyle>, textStyles: Pair<TextStyle, TextStyle>,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
textColor: Color? = null
) { ) {
val balanceSplit = splitBalance(balanceString)
val content = val content =
buildAnnotatedString { buildAnnotatedString {
withStyle( withStyle(
style = textStyles.first.toSpanStyle() style = textStyles.first.toSpanStyle()
) { ) {
append(mainPart) append(balanceSplit.first)
} }
withStyle( withStyle(
style = textStyles.second.toSpanStyle() style = textStyles.second.toSpanStyle()
) { ) {
append(secondPart) append(balanceSplit.second)
} }
} }
Text( if (textColor != null) {
text = content, Text(
// fixme color text = content,
color = MaterialTheme.colorScheme.onBackground, color = textColor,
maxLines = 1, maxLines = 1,
modifier = modifier =
Modifier Modifier
.basicMarquee() .basicMarquee()
.then(modifier) .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 @Composable

View File

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

View File

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

View File

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

View File

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

View File

@ -71,7 +71,7 @@ internal class MockSynchronizer : CloseableSynchronizer {
override val transactions: Flow<List<TransactionOverview>> override val transactions: Flow<List<TransactionOverview>>
get() = TODO("Not yet implemented") 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.") get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
override fun close() { override fun close() {
@ -98,7 +98,7 @@ internal class MockSynchronizer : CloseableSynchronizer {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.") 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.") 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.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.test.junit4.ComposeContentTestRule 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.Zatoshi
import cash.z.ecc.android.sdk.model.ZecSend import cash.z.ecc.android.sdk.model.ZecSend
import co.electriccoin.zcash.ui.design.theme.ZcashTheme import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@ -101,8 +101,7 @@ class SendViewTestSetup(
walletSnapshot = walletSnapshot =
WalletSnapshotFixture.new( WalletSnapshotFixture.new(
saplingBalance = saplingBalance =
WalletBalance( WalletBalanceFixture.new(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)) 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.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.test.filters.MediumTest 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.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.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.fixture.ZecSendFixture import cash.z.ecc.sdk.fixture.ZecSendFixture
@ -44,8 +44,7 @@ class SendViewIntegrationTest {
private val walletSnapshot = private val walletSnapshot =
WalletSnapshotFixture.new( WalletSnapshotFixture.new(
saplingBalance = saplingBalance =
WalletBalance( WalletBalanceFixture.new(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)) 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.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview 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.WalletBalance
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.type.ZcashCurrency 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.WalletSnapshot
import co.electriccoin.zcash.ui.common.model.spendableBalance import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.totalBalance 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.component.StyledBalance
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.fixture.WalletSnapshotFixture
import java.util.Locale
@Preview(device = Devices.PIXEL_2) @Preview(device = Devices.PIXEL_2)
@Composable @Composable
@ -49,7 +46,8 @@ private fun BalanceWidgetPreview() {
saplingBalance = saplingBalance =
WalletBalance( WalletBalance(
Zatoshi(1234567891234567), Zatoshi(1234567891234567),
Zatoshi(123456789) Zatoshi(123456789),
Zatoshi(123)
) )
), ),
isReferenceToBalances = true, 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 @Composable
@Suppress("LongMethod") @Suppress("LongMethod")
fun BalanceWidget( fun BalanceWidget(
@ -95,18 +73,15 @@ fun BalanceWidget(
.then(modifier), .then(modifier),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
val totalBalanceSplit = splitBalance(walletSnapshot.totalBalance().toZecString())
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
StyledBalance( StyledBalance(
mainPart = totalBalanceSplit.first, balanceString = walletSnapshot.totalBalance().toZecString(),
secondPart = totalBalanceSplit.second,
textStyles = textStyles =
Pair( Pair(
ZcashTheme.extendedTypography.balanceStyles.first, ZcashTheme.extendedTypography.balanceWidgetStyles.first,
ZcashTheme.extendedTypography.balanceStyles.second ZcashTheme.extendedTypography.balanceWidgetStyles.second
) )
) )
@ -136,15 +111,12 @@ fun BalanceWidget(
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny)) Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
val availableBalanceSplit = splitBalance(walletSnapshot.spendableBalance().toZecString())
StyledBalance( StyledBalance(
mainPart = availableBalanceSplit.first, balanceString = walletSnapshot.spendableBalance().toZecString(),
secondPart = availableBalanceSplit.second,
textStyles = textStyles =
Pair( Pair(
ZcashTheme.extendedTypography.balanceStyles.third, ZcashTheme.extendedTypography.balanceWidgetStyles.third,
ZcashTheme.extendedTypography.balanceStyles.fourth ZcashTheme.extendedTypography.balanceWidgetStyles.fourth
) )
) )

View File

@ -15,7 +15,7 @@ data class WalletSnapshot(
val processorInfo: CompactBlockProcessor.ProcessorInfo, val processorInfo: CompactBlockProcessor.ProcessorInfo,
val orchardBalance: WalletBalance, val orchardBalance: WalletBalance,
val saplingBalance: WalletBalance, val saplingBalance: WalletBalance,
val transparentBalance: WalletBalance, val transparentBalance: Zatoshi,
val progress: PercentDecimal, val progress: PercentDecimal,
val synchronizerError: SynchronizerError? val synchronizerError: SynchronizerError?
) { ) {
@ -28,9 +28,15 @@ data class WalletSnapshot(
val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds 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. // 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 // 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. // to transfer their balance to the latest balance type to make it spendable.
fun WalletSnapshot.spendableBalance() = orchardBalance.available + saplingBalance.available 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 // 3
saplingBalances, saplingBalances,
// 4 // 4
transparentBalances, transparentBalance,
// 5 // 5
progress, progress,
// 6 // 6
@ -386,16 +386,16 @@ private fun Synchronizer.toWalletSnapshot() =
) { flows -> ) { flows ->
val orchardBalance = flows[2] as WalletBalance? val orchardBalance = flows[2] as WalletBalance?
val saplingBalance = flows[3] 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 val progressPercentDecimal = flows[5] as PercentDecimal
WalletSnapshot( WalletSnapshot(
flows[0] as Synchronizer.Status, flows[0] as Synchronizer.Status,
flows[1] as CompactBlockProcessor.ProcessorInfo, flows[1] as CompactBlockProcessor.ProcessorInfo,
orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), orchardBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), saplingBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0), Zatoshi(0)),
transparentBalance ?: WalletBalance(Zatoshi(0), Zatoshi(0)), transparentBalance ?: Zatoshi(0),
progressPercentDecimal, progressPercentDecimal,
flows[6] as SynchronizerError? 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.Synchronizer
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor 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.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi 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.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
@ -12,9 +14,9 @@ import co.electriccoin.zcash.ui.common.viewmodel.SynchronizerError
object WalletSnapshotFixture { object WalletSnapshotFixture {
val STATUS = Synchronizer.Status.SYNCED val STATUS = Synchronizer.Status.SYNCED
val PROGRESS = PercentDecimal.ZERO_PERCENT val PROGRESS = PercentDecimal.ZERO_PERCENT
val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1)) val TRANSPARENT_BALANCE: Zatoshi = ZatoshiFixture.new(8)
val ORCHARD_BALANCE: WalletBalance = WalletBalance(Zatoshi(5), Zatoshi(2)) val ORCHARD_BALANCE: WalletBalance = WalletBalanceFixture.newLong(8, 2, 1)
val SAPLING_BALANCE: WalletBalance = WalletBalance(Zatoshi(4), Zatoshi(4)) 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 // Should fill in with non-empty values for better example values in tests and UI previews
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -28,7 +30,7 @@ object WalletSnapshotFixture {
), ),
orchardBalance: WalletBalance = ORCHARD_BALANCE, orchardBalance: WalletBalance = ORCHARD_BALANCE,
saplingBalance: WalletBalance = SAPLING_BALANCE, saplingBalance: WalletBalance = SAPLING_BALANCE,
transparentBalance: WalletBalance = TRANSPARENT_BALANCE, transparentBalance: Zatoshi = TRANSPARENT_BALANCE,
progress: PercentDecimal = PROGRESS, progress: PercentDecimal = PROGRESS,
synchronizerError: SynchronizerError? = null synchronizerError: SynchronizerError? = null
) = WalletSnapshot( ) = WalletSnapshot(

View File

@ -1,32 +1,49 @@
package co.electriccoin.zcash.ui.screen.balances.view package co.electriccoin.zcash.ui.screen.balances.view
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview 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.R
import co.electriccoin.zcash.ui.common.BalanceWidget import co.electriccoin.zcash.ui.common.BalanceWidget
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.spendableBalance
import co.electriccoin.zcash.ui.common.model.valuePendingBalance
import co.electriccoin.zcash.ui.common.test.CommonTag import co.electriccoin.zcash.ui.common.test.CommonTag
import co.electriccoin.zcash.ui.design.component.Body 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.CircularScreenProgressIndicator
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.SmallTopAppBar 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.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.model.WalletDisplayValues import co.electriccoin.zcash.ui.screen.account.model.WalletDisplayValues
@ -106,7 +123,7 @@ private fun BalancesMainContent(
modifier = modifier =
Modifier Modifier
.fillMaxHeight() .fillMaxHeight()
// .verticalScroll(rememberScrollState()) Uncomment this once the whole screen UI is implemented .verticalScroll(rememberScrollState())
.then(modifier), .then(modifier),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
@ -118,20 +135,157 @@ private fun BalancesMainContent(
isReferenceToBalances = false, isReferenceToBalances = false,
onReferenceClick = {} 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( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
Body( Body(
text = stringResource(id = R.string.balances_coming_soon), text = stringResource(id = R.string.balances_coming_soon),
textAlign = TextAlign.Center 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"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="balances_title">Balances</string> <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> funds shielding,\nBlock synchronization indicator</string>
</resources> </resources>