[#1195] Integrate Current Balances widget to Send

- Closes #1195
- The fixture recipient address type changed from the unified to the sapling on several tests to avoid temporary address TODO text obtained from the SDK fixture
- WalletDisplayValues have the updateAvailable optional with false as a default value
- Changelog updated
This commit is contained in:
Honza Rychnovský 2024-01-26 12:44:46 +01:00 committed by GitHub
parent 1944916606
commit 7e27bdddff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 82 additions and 45 deletions

View File

@ -12,6 +12,7 @@ directly impact users rather than highlighting other key architectural updates.*
### Added
- The current balance UI on top of the Account screen has been reworked. It now displays the currently available
balance as well.
- The same current balance UI was also incorporated into the Send Form screen.
## [0.2.0 (530)] - 2024-01-16

View File

@ -7,7 +7,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZecSend
object ZecSendFixture {
const val ADDRESS: String = WalletAddressFixture.UNIFIED_ADDRESS_STRING
const val ADDRESS: String = WalletAddressFixture.SAPLING_ADDRESS_STRING
@Suppress("MagicNumber")
val AMOUNT = Zatoshi(123)

View File

@ -52,7 +52,8 @@ internal fun ComposeContentTestRule.setAmount(amount: String) {
internal fun ComposeContentTestRule.setValidAddress() {
onNodeWithText(getStringResource(R.string.send_to)).also {
it.performTextClearance()
it.performTextInput(WalletAddressFixture.UNIFIED_ADDRESS_STRING)
// Using sapling address here, as the unified is not available in the fixture. This will change.
it.performTextInput(WalletAddressFixture.SAPLING_ADDRESS_STRING)
}
}

View File

@ -5,10 +5,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.send.ext.Saver
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
import co.electriccoin.zcash.ui.screen.send.model.SendStage
@ -96,13 +97,24 @@ class SendViewTestSetup(
ZcashTheme {
Send(
mySpendableBalance = ZatoshiFixture.new(Zatoshi.MAX_INCLUSIVE),
walletSnapshot =
WalletSnapshotFixture.new(
saplingBalance =
WalletBalance(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100))
)
),
sendStage = sendStage,
sendArgumentsWrapper = initialSendArgumentWrapper,
onSendStageChange = setSendStage,
zecSend = zecSend,
onZecSendChange = setZecSend,
onBack = onBackAction,
goBalances = {
// TODO [#1194]: Cover Current balances UI widget with tests
// TODO [#1194]: https://github.com/Electric-Coin-Company/zashi-android/issues/1194
},
onSettings = { onSettingsCount.incrementAndGet() },
onCreateAndSend = {
onCreateCount.incrementAndGet()

View File

@ -6,12 +6,13 @@ 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.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.ZatoshiFixture
import cash.z.ecc.sdk.fixture.ZecSendFixture
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.fixture.MockSynchronizer
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.send.WrapSend
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
import co.electriccoin.zcash.ui.screen.send.assertOnForm
@ -39,7 +40,14 @@ class SendViewIntegrationTest {
)
}
private val synchronizer = MockSynchronizer.new()
private val balance = ZatoshiFixture.new(Zatoshi.MAX_INCLUSIVE)
private val walletSnapshot =
WalletSnapshotFixture.new(
saplingBalance =
WalletBalance(
total = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100)),
available = Zatoshi(Zatoshi.MAX_INCLUSIVE.div(100))
)
)
@Test
@MediumTest
@ -54,10 +62,11 @@ class SendViewIntegrationTest {
WrapSend(
sendArgumentsWrapper = null,
synchronizer = synchronizer,
spendableBalance = balance,
walletSnapshot = walletSnapshot,
spendingKey = spendingKey,
goToQrScanner = {},
goBack = {},
goBalances = {},
hasCameraFeature = true,
goSettings = {}
)

View File

@ -107,7 +107,7 @@ class SendViewTest : UiTestPrerequisites() {
launch {
testSetup.getLastZecSend().also {
assertNotNull(it)
assertEquals(WalletAddressFixture.unified(), it.destination)
assertEquals(WalletAddressFixture.sapling(), it.destination)
assertEquals(Zatoshi(12345678900000), it.amount)
assertEquals(ZecRequestFixture.MESSAGE.value, it.memo.value)
}
@ -143,7 +143,7 @@ class SendViewTest : UiTestPrerequisites() {
launch {
testSetup.getLastZecSend().also {
assertNotNull(it)
assertEquals(WalletAddressFixture.unified(), it.destination)
assertEquals(WalletAddressFixture.sapling(), it.destination)
assertEquals(Zatoshi(12345678900000), it.amount)
assertEquals(ZecRequestFixture.MESSAGE.value, it.memo.value)
}
@ -201,7 +201,7 @@ class SendViewTest : UiTestPrerequisites() {
launch {
testSetup.getLastZecSend().also {
assertNotNull(it)
assertEquals(WalletAddressFixture.unified(), it.destination)
assertEquals(WalletAddressFixture.sapling(), it.destination)
assertEquals(Zatoshi(12345678900000), it.amount)
assertEquals(ZecRequestFixture.MESSAGE.value, it.memo.value)
}
@ -280,7 +280,7 @@ class SendViewTest : UiTestPrerequisites() {
launch {
testSetup.getLastZecSend().also {
assertNotNull(it)
assertEquals(WalletAddressFixture.unified(), it.destination)
assertEquals(WalletAddressFixture.sapling(), it.destination)
assertEquals(Zatoshi(12345600000), it.amount)
assertTrue(it.memo.value.isEmpty())
}

View File

@ -27,7 +27,7 @@ data class WalletDisplayValues(
internal fun getNextValues(
context: Context,
walletSnapshot: WalletSnapshot,
updateAvailable: Boolean
updateAvailable: Boolean = false
): WalletDisplayValues {
var progress = PercentDecimal.ZERO_PERCENT
val zecAmountText = walletSnapshot.totalBalance().toZecString()

View File

@ -166,8 +166,6 @@ private fun Status(
.testTag(AccountTag.STATUS_VIEWS),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
if (walletDisplayValues.zecAmountText.isNotEmpty()) {
BalanceWidget(
walletSnapshot = walletSnapshot,

View File

@ -102,6 +102,7 @@ internal fun WrapHome(
activity = activity,
goToQrScanner = goScan,
goBack = homeGoBack,
goBalances = { forceHomePageIndexFlow.tryEmit(ForcePage(HomeScreenIndex.BALANCES)) },
goSettings = goSettings,
sendArgumentsWrapper = sendArgumentsWrapper
)

View File

@ -14,11 +14,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.sdk.extension.send
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.model.spendableBalance
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.screen.send.ext.Saver
@ -28,11 +27,13 @@ import co.electriccoin.zcash.ui.screen.send.view.Send
import kotlinx.coroutines.launch
@Composable
@Suppress("LongParameterList")
internal fun WrapSend(
activity: ComponentActivity,
sendArgumentsWrapper: SendArgumentsWrapper?,
goToQrScanner: () -> Unit,
goBack: () -> Unit,
goBalances: () -> Unit,
goSettings: () -> Unit,
) {
val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
@ -41,17 +42,18 @@ internal fun WrapSend(
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
val spendableBalance = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value?.spendableBalance()
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
WrapSend(
sendArgumentsWrapper,
synchronizer,
spendableBalance,
walletSnapshot,
spendingKey,
goToQrScanner,
goBack,
goBalances,
goSettings,
hasCameraFeature
)
@ -63,10 +65,11 @@ internal fun WrapSend(
internal fun WrapSend(
sendArgumentsWrapper: SendArgumentsWrapper?,
synchronizer: Synchronizer?,
spendableBalance: Zatoshi?,
walletSnapshot: WalletSnapshot?,
spendingKey: UnifiedSpendingKey?,
goToQrScanner: () -> Unit,
goBack: () -> Unit,
goBalances: () -> Unit,
goSettings: () -> Unit,
hasCameraFeature: Boolean
) {
@ -99,14 +102,14 @@ internal fun WrapSend(
onBackAction()
}
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
if (null == synchronizer || null == walletSnapshot || null == spendingKey) {
// 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]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146
CircularScreenProgressIndicator()
} else {
Send(
mySpendableBalance = spendableBalance,
walletSnapshot = walletSnapshot,
sendArgumentsWrapper = sendArgumentsWrapper,
sendStage = sendStage,
onSendStageChange = setSendStage,
@ -129,6 +132,7 @@ internal fun WrapSend(
}
},
onQrScannerOpen = goToQrScanner,
goBalances = goBalances,
hasCameraFeature = hasCameraFeature
)
}

View File

@ -7,10 +7,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
@ -55,6 +53,9 @@ import cash.z.ecc.sdk.fixture.MemoFixture
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.spackle.Twig
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.spendableBalance
import co.electriccoin.zcash.ui.common.test.CommonTag
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.Body
@ -65,6 +66,8 @@ import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.model.WalletDisplayValues
import co.electriccoin.zcash.ui.screen.send.SendTag
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
@ -80,7 +83,7 @@ private fun PreviewSendForm() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
Send(
mySpendableBalance = ZatoshiFixture.new(),
walletSnapshot = WalletSnapshotFixture.new(),
sendArgumentsWrapper = null,
sendStage = SendStage.Form,
onSendStageChange = {},
@ -90,6 +93,7 @@ private fun PreviewSendForm() {
onQrScannerOpen = {},
onBack = {},
onSettings = {},
goBalances = {},
hasCameraFeature = true
)
}
@ -153,7 +157,7 @@ private fun PreviewSendConfirmation() {
@Suppress("LongParameterList")
@Composable
fun Send(
mySpendableBalance: Zatoshi,
walletSnapshot: WalletSnapshot,
sendArgumentsWrapper: SendArgumentsWrapper?,
sendStage: SendStage,
onSendStageChange: (SendStage) -> Unit,
@ -163,6 +167,7 @@ fun Send(
onSettings: () -> Unit,
onCreateAndSend: (ZecSend) -> Unit,
onQrScannerOpen: () -> Unit,
goBalances: () -> Unit,
hasCameraFeature: Boolean
) {
Scaffold(topBar = {
@ -173,7 +178,7 @@ fun Send(
)
}) { paddingValues ->
SendMainContent(
myBalance = mySpendableBalance,
walletSnapshot = walletSnapshot,
sendArgumentsWrapper = sendArgumentsWrapper,
onBack = onBack,
sendStage = sendStage,
@ -182,6 +187,7 @@ fun Send(
onZecSendChange = onZecSendChange,
onSendSubmit = onCreateAndSend,
onQrScannerOpen = onQrScannerOpen,
goBalances = goBalances,
hasCameraFeature = hasCameraFeature,
modifier =
Modifier
@ -228,7 +234,7 @@ private fun SendTopAppBar(
@Suppress("LongParameterList")
@Composable
private fun SendMainContent(
myBalance: Zatoshi,
walletSnapshot: WalletSnapshot,
sendArgumentsWrapper: SendArgumentsWrapper?,
zecSend: ZecSend?,
onZecSendChange: (ZecSend) -> Unit,
@ -237,13 +243,14 @@ private fun SendMainContent(
onSendStageChange: (SendStage) -> Unit,
onSendSubmit: (ZecSend) -> Unit,
onQrScannerOpen: () -> Unit,
goBalances: () -> Unit,
hasCameraFeature: Boolean,
modifier: Modifier = Modifier
) {
when {
(sendStage == SendStage.Form || null == zecSend) -> {
SendForm(
myBalance = myBalance,
walletSnapshot = walletSnapshot,
sendArgumentsWrapper = sendArgumentsWrapper,
previousZecSend = zecSend,
onCreateZecSend = {
@ -251,6 +258,7 @@ private fun SendMainContent(
onZecSendChange(it)
},
onQrScannerOpen = onQrScannerOpen,
goBalances = goBalances,
hasCameraFeature = hasCameraFeature,
modifier = modifier
)
@ -296,11 +304,12 @@ private fun SendMainContent(
@Suppress("LongMethod", "LongParameterList", "CyclomaticComplexMethod")
@Composable
private fun SendForm(
myBalance: Zatoshi,
walletSnapshot: WalletSnapshot,
sendArgumentsWrapper: SendArgumentsWrapper?,
previousZecSend: ZecSend?,
onCreateZecSend: (ZecSend) -> Unit,
onQrScannerOpen: () -> Unit,
goBalances: () -> Unit,
hasCameraFeature: Boolean,
modifier: Modifier = Modifier
) {
@ -340,24 +349,28 @@ private fun SendForm(
Column(
modifier =
Modifier
.imePadding()
.fillMaxSize()
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.then(modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
Header(
text = stringResource(id = R.string.send_balance, myBalance.toZecString()),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Body(
text = stringResource(id = R.string.send_balance_subtitle),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
val walletDisplayValues =
WalletDisplayValues.getNextValues(
context = LocalContext.current,
walletSnapshot = walletSnapshot
)
Spacer(modifier = Modifier.height(dimens.spacingLarge))
Spacer(modifier = Modifier.height(dimens.spacingDefault))
if (walletDisplayValues.zecAmountText.isNotEmpty()) {
BalanceWidget(
walletSnapshot = walletSnapshot,
isReferenceToBalances = true,
onReferenceClick = goBalances
)
}
Spacer(modifier = Modifier.height(dimens.spacingXlarge))
FormTextField(
value = recipientAddressString,
@ -487,7 +500,7 @@ private fun SendForm(
val sendButtonEnabled =
amountZecString.isNotBlank() &&
sendValueCheck > 0L &&
myBalance.value >= (sendValueCheck + ZcashSdk.MINERS_FEE.value) &&
walletSnapshot.spendableBalance().value >= (sendValueCheck + ZcashSdk.MINERS_FEE.value) &&
recipientAddressString.length > ABBREVIATION_INDEX
PrimaryButton(

View File

@ -3,8 +3,6 @@
<string name="send_back">Back</string>
<string name="send_back_content_description">Back</string>
<string name="send_scan_content_description">Scan</string>
<string name="send_balance" formatted="true"><xliff:g id="balance" example="12.345">%1$s</xliff:g> ZEC Available</string>
<string name="send_balance_subtitle">Additional funds may be in transit.</string>
<string name="send_to">Who would you like to send ZEC to?</string>
<string name="send_amount">How much?</string>
<string name="send_memo">Memo</string>