[#1416] Shielded transaction UI

* [#1416] Shielded transaction UI

* Shielded transaction design update

* [#1416] Design updates

Closes #1416

* [#1416] Code cleanup

Closes #1416

* [#1416] Documentation update

Closes #1416

* [#1416] Code cleanup

Closes #1416

* [#1416] Shielding icon fix

#Closes #1416

* [#1416] Resources update

#Closes #1416

* Add Spanish whatsnew

* [#1416] SDK snapshot

Closes #1416

* Fix failing tests

* Fix changelogs entry

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Milan 2024-11-12 20:15:23 +01:00 committed by GitHub
parent c6350641e3
commit 60fa9268e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 124 additions and 46 deletions

View File

@ -11,7 +11,9 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
- Zashi app now supports Spanish language - Zashi app now supports Spanish language
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI - The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
### Changed ### Changelog
- Shielded transactions are properly indicated in transaction history
- The in-app update logic has been fixed and is now correctly requested with every app launch
- The Not enough space and In-app udpate screens have been redesigned - The Not enough space and In-app udpate screens have been redesigned
### Fixed ### Fixed

View File

@ -14,7 +14,9 @@ directly impact users rather than highlighting other key architectural updates.*
- Zashi app now supports Spanish language - Zashi app now supports Spanish language
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI - The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
### Changed ### Changelog
- Shielded transactions are properly indicated in transaction history
- The in-app update logic has been fixed and is now correctly requested with every app launch
- The Not enough space and In-app udpate screens have been redesigned - The Not enough space and In-app udpate screens have been redesigned
### Fixed ### Fixed

View File

@ -14,11 +14,13 @@ directly impact users rather than highlighting other key architectural updates.*
- Zashi app now supports Spanish language - Zashi app now supports Spanish language
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI - The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
### Changelog
- Shielded transactions are properly indicated in transaction history
- The in-app update logic has been fixed and is now correctly requested with every app launch
- The Not enough space and In-app udpate screens have been redesigned
### Fixed ### Fixed
- Address book toast now correctly shows on send screen when adding both new and known addresses to text field - Address book toast now correctly shows on send screen when adding both new and known addresses to text field
- The application now correctly navigates to the homepage after deleting the current wallet and creating a new or - The application now correctly navigates to the homepage after deleting the current wallet and creating a new or
recovering an older one recovering an older one
- The in-app update logic has been fixed and is now correctly requested with every app launch - The in-app update logic has been fixed and is now correctly requested with every app launch
### Changed
- The Not enough space and In-app udpate screens have been redesigned

View File

@ -219,7 +219,7 @@ ZCASH_BIP39_VERSION=1.0.8
FLEXA_VERSION=1.0.5 FLEXA_VERSION=1.0.5
# 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.2.5 ZCASH_SDK_VERSION=2.2.5-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

@ -10,6 +10,7 @@ import cash.z.ecc.android.sdk.model.FastestServersResult
import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOutput
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.TransactionSubmitResult
@ -234,6 +235,10 @@ 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 getTransactionOutputs(transactionOverview: TransactionOverview): List<TransactionOutput> {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
companion object { companion object {
fun new() = MockSynchronizer() fun new() = MockSynchronizer()
} }

View File

@ -15,17 +15,20 @@ internal object TransactionHistorySyncStateFixture {
TransactionOverviewExt( TransactionOverviewExt(
TransactionOverviewFixture.new(), TransactionOverviewFixture.new(),
TransactionRecipient.Account(Account.DEFAULT), TransactionRecipient.Account(Account.DEFAULT),
AddressType.Shielded AddressType.Shielded,
emptyList()
), ),
TransactionOverviewExt( TransactionOverviewExt(
TransactionOverviewFixture.new(), TransactionOverviewFixture.new(),
TransactionRecipient.Account(Account(1)), TransactionRecipient.Account(Account(1)),
AddressType.Transparent AddressType.Transparent,
emptyList()
), ),
TransactionOverviewExt( TransactionOverviewExt(
TransactionOverviewFixture.new(), TransactionOverviewFixture.new(),
null, null,
AddressType.Unified AddressType.Unified,
emptyList()
), ),
) )
val STATE = TransactionHistorySyncState.Syncing(TRANSACTIONS) val STATE = TransactionHistorySyncState.Syncing(TRANSACTIONS)

View File

@ -108,6 +108,8 @@ class WalletViewModel(
it.getSortHeight(networkHeight) it.getSortHeight(networkHeight)
} }
.map { .map {
val outputs = synchronizer.getTransactionOutputs(it)
if (it.isSentTransaction) { if (it.isSentTransaction) {
val recipient = synchronizer.getRecipients(it).firstOrNull() val recipient = synchronizer.getRecipients(it).firstOrNull()
TransactionOverviewExt( TransactionOverviewExt(
@ -118,14 +120,16 @@ class WalletViewModel(
synchronizer.validateAddress(recipient.addressValue) synchronizer.validateAddress(recipient.addressValue)
} else { } else {
null null
} },
transactionOutputs = outputs,
) )
} else { } else {
// Note that recipients can only be queried for sent transactions // Note that recipients can only be queried for sent transactions
TransactionOverviewExt( TransactionOverviewExt(
overview = it, overview = it,
recipient = null, recipient = null,
recipientAddressType = null recipientAddressType = null,
transactionOutputs = outputs,
) )
} }
} }

View File

@ -1,6 +1,7 @@
package co.electriccoin.zcash.ui.screen.account.ext package co.electriccoin.zcash.ui.screen.account.ext
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.TransactionOutput
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.type.AddressType import cash.z.ecc.android.sdk.type.AddressType
@ -8,7 +9,8 @@ import cash.z.ecc.android.sdk.type.AddressType
data class TransactionOverviewExt( data class TransactionOverviewExt(
val overview: TransactionOverview, val overview: TransactionOverview,
val recipient: TransactionRecipient?, val recipient: TransactionRecipient?,
val recipientAddressType: AddressType? val recipientAddressType: AddressType?,
val transactionOutputs: List<TransactionOutput>
) )
/** /**

View File

@ -2,6 +2,7 @@ package co.electriccoin.zcash.ui.screen.account.fixture
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
import cash.z.ecc.android.sdk.fixture.WalletFixture import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.TransactionOutput
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -26,6 +27,8 @@ object TransactionUiFixture {
val ADDRESS_BOOK_CONTACT: AddressBookContact? = null val ADDRESS_BOOK_CONTACT: AddressBookContact? = null
val OUTPUTS: List<TransactionOutput> = emptyList()
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal fun new( internal fun new(
overview: TransactionOverview = OVERVIEW, overview: TransactionOverview = OVERVIEW,
@ -33,13 +36,15 @@ object TransactionUiFixture {
recipientAddressType: AddressType = RECIPIENT_ADDRESS_TYPE, recipientAddressType: AddressType = RECIPIENT_ADDRESS_TYPE,
expandableState: TrxItemState = EXPANDABLE_STATE, expandableState: TrxItemState = EXPANDABLE_STATE,
messages: List<String> = MESSAGES, messages: List<String> = MESSAGES,
addressBookContact: AddressBookContact? = ADDRESS_BOOK_CONTACT addressBookContact: AddressBookContact? = ADDRESS_BOOK_CONTACT,
outputs: List<TransactionOutput> = OUTPUTS
) = TransactionUi( ) = TransactionUi(
overview = overview, overview = overview,
recipient = recipient, recipient = recipient,
recipientAddressType = recipientAddressType, recipientAddressType = recipientAddressType,
expandableState = expandableState, expandableState = expandableState,
messages = messages, messages = messages,
addressBookContact = addressBookContact addressBookContact = addressBookContact,
outputs = outputs
) )
} }

View File

@ -1,5 +1,6 @@
package co.electriccoin.zcash.ui.screen.account.model package co.electriccoin.zcash.ui.screen.account.model
import cash.z.ecc.android.sdk.model.TransactionOutput
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.type.AddressType import cash.z.ecc.android.sdk.type.AddressType
@ -12,21 +13,23 @@ data class TransactionUi(
val recipientAddressType: AddressType?, val recipientAddressType: AddressType?,
val expandableState: TrxItemState, val expandableState: TrxItemState,
val messages: List<String>?, val messages: List<String>?,
val addressBookContact: AddressBookContact? val addressBookContact: AddressBookContact?,
val outputs: List<TransactionOutput>,
) { ) {
companion object { companion object {
fun new( fun new(
data: TransactionOverviewExt, data: TransactionOverviewExt,
expandableState: TrxItemState, expandableState: TrxItemState,
messages: List<String>?, messages: List<String>?,
addressBookContact: AddressBookContact? addressBookContact: AddressBookContact?,
) = TransactionUi( ) = TransactionUi(
overview = data.overview, overview = data.overview,
recipient = data.recipient, recipient = data.recipient,
recipientAddressType = data.recipientAddressType, recipientAddressType = data.recipientAddressType,
expandableState = expandableState, expandableState = expandableState,
messages = messages, messages = messages,
addressBookContact = addressBookContact addressBookContact = addressBookContact,
outputs = data.transactionOutputs
) )
} }
} }

View File

@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionPool
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionState import cash.z.ecc.android.sdk.model.TransactionState
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
@ -316,7 +317,7 @@ private fun ComposableHistoryListItemsPreview() {
const val ADDRESS_IN_TITLE_WIDTH_RATIO = 0.5f const val ADDRESS_IN_TITLE_WIDTH_RATIO = 0.5f
@Composable @Composable
@Suppress("LongMethod") @Suppress("LongMethod", "CyclomaticComplexMethod")
private fun HistoryItem( private fun HistoryItem(
transaction: TransactionUi, transaction: TransactionUi,
isHideBalances: Boolean, isHideBalances: Boolean,
@ -375,6 +376,30 @@ private fun HistoryItem(
textDecoration = TextDecoration.LineThrough textDecoration = TextDecoration.LineThrough
) )
} }
TransactionExtendedState.SHIELDED -> {
typeText = stringResource(id = R.string.account_history_item_shielded_funds)
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_shielded_funds)
textColor = ZashiColors.Text.textPrimary
textStyle = ZashiTypography.textSm
}
TransactionExtendedState.SHIELDING -> {
typeText = stringResource(id = R.string.account_history_item_shielding_funds)
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_shielded_funds)
textColor = ZashiColors.Text.textPrimary
textStyle = ZashiTypography.textSm
}
TransactionExtendedState.SHIELDING_FAILED -> {
typeText = stringResource(id = R.string.account_history_item_shielding_failed)
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_shielded_funds)
textColor = ZashiColors.Text.textError
textStyle =
ZashiTypography.textSm.copy(
textDecoration = TextDecoration.LineThrough
)
}
} }
Row( Row(
@ -581,7 +606,10 @@ private fun HistoryItemCollapsedAddressPart(
) )
} }
} }
} else { } else if (
transaction.outputs.none { it.pool == TransactionPool.TRANSPARENT } &&
!transaction.overview.isShielding
) {
Icon( Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_trx_shielded), imageVector = ImageVector.vectorResource(R.drawable.ic_trx_shielded),
contentDescription = stringResource(id = R.string.account_history_item_shielded) contentDescription = stringResource(id = R.string.account_history_item_shielded)
@ -689,7 +717,7 @@ private fun HistoryItemExpandedPart(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
if (transaction.messages.containsValidMemo()) { if (transaction.overview.isShielding.not() && transaction.messages.containsValidMemo()) {
Text( Text(
text = text =
pluralStringResource( pluralStringResource(
@ -974,41 +1002,41 @@ internal enum class TransactionExtendedState {
SEND_FAILED, SEND_FAILED,
RECEIVED, RECEIVED,
RECEIVING, RECEIVING,
RECEIVE_FAILED; RECEIVE_FAILED,
SHIELDED,
SHIELDING,
SHIELDING_FAILED;
fun isFailed(): Boolean = this == SEND_FAILED || this == RECEIVE_FAILED fun isShielding() = this in listOf(SHIELDED, RECEIVE_FAILED, SHIELDING_FAILED)
fun isSendType(): Boolean = this == SENDING || this == SENT || this == SEND_FAILED fun isFailed(): Boolean = this in listOf(SEND_FAILED, RECEIVE_FAILED, SHIELDING_FAILED)
fun isSendType(): Boolean = this in listOf(SENDING, SENT, SEND_FAILED, SHIELDED, SHIELDING_FAILED, SHIELDING)
} }
private fun TransactionOverview.getExtendedState(): TransactionExtendedState { private fun TransactionOverview.getExtendedState(): TransactionExtendedState {
return when (transactionState) { return when (transactionState) {
TransactionState.Expired -> { TransactionState.Expired ->
if (isSentTransaction) { when {
TransactionExtendedState.SEND_FAILED isShielding -> TransactionExtendedState.SHIELDING_FAILED
} else { isSentTransaction -> TransactionExtendedState.SEND_FAILED
TransactionExtendedState.RECEIVE_FAILED else -> TransactionExtendedState.RECEIVE_FAILED
} }
}
TransactionState.Confirmed -> { TransactionState.Confirmed ->
if (isSentTransaction) { when {
TransactionExtendedState.SENT isShielding -> TransactionExtendedState.SHIELDED
} else { isSentTransaction -> TransactionExtendedState.SENT
TransactionExtendedState.RECEIVED else -> TransactionExtendedState.RECEIVED
} }
}
TransactionState.Pending -> { TransactionState.Pending ->
if (isSentTransaction) { when {
TransactionExtendedState.SENDING isShielding -> TransactionExtendedState.SHIELDING
} else { isSentTransaction -> TransactionExtendedState.SENDING
TransactionExtendedState.RECEIVING else -> TransactionExtendedState.RECEIVING
} }
}
else -> { else -> error("Unexpected transaction state found while calculating its extended state.")
error("Unexpected transaction state found while calculating its extended state.")
}
} }
} }

View File

@ -131,7 +131,7 @@ class TransactionHistoryViewModel(
data = data, data = data,
expandableState = existingTransaction?.expandableState ?: TrxItemState.COLLAPSED, expandableState = existingTransaction?.expandableState ?: TrxItemState.COLLAPSED,
messages = existingTransaction?.messages, messages = existingTransaction?.messages,
addressBookContact = addressBookContact addressBookContact = addressBookContact,
) )
} }

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="19dp"
android:height="18dp"
android:viewportWidth="19"
android:viewportHeight="18">
<group>
<clip-path
android:pathData="M5.979,0l12.774,8.126l-5.979,9.399l-12.774,-8.126z"/>
<path
android:pathData="M16.174,6.853C16.209,6.649 16.121,6.451 15.944,6.339L10.325,2.764C10.215,2.694 10.087,2.672 9.949,2.703C9.811,2.734 9.704,2.808 9.634,2.918C9.565,3.027 9.537,3.164 9.573,3.293C9.601,3.418 9.678,3.538 9.788,3.608L14.04,6.313L4.548,6.367L2.102,6.375C1.84,6.433 1.659,6.698 1.721,6.974C1.782,7.25 2.049,7.408 2.32,7.355L15.792,7.249C15.985,7.206 16.145,7.047 16.174,6.853Z"
android:fillColor="#000000"/>
<path
android:pathData="M17.028,10.56C17,10.436 16.923,10.315 16.813,10.245C16.703,10.176 16.567,10.148 16.437,10.184L2.962,10.276C2.769,10.319 2.609,10.478 2.58,10.673C2.545,10.876 2.633,11.074 2.801,11.181L8.421,14.756C8.53,14.826 8.667,14.853 8.797,14.817C8.921,14.79 9.041,14.712 9.111,14.602C9.181,14.493 9.208,14.356 9.172,14.227C9.145,14.102 9.067,13.982 8.957,13.912L4.815,11.277L16.652,11.151C16.914,11.092 17.094,10.828 17.033,10.552L17.028,10.56Z"
android:fillColor="#000000"/>
</group>
</vector>

View File

@ -3,6 +3,9 @@
<string name="account_history_empty">Sin historial de transacciones</string> <string name="account_history_empty">Sin historial de transacciones</string>
<string name="account_history_item_sent">Enviado</string> <string name="account_history_item_sent">Enviado</string>
<string name="account_history_item_shielded_funds">Fondos Protegidos</string>
<string name="account_history_item_shielding_funds">Protegiendo fondos…</string>
<string name="account_history_item_shielding_failed">Fondos Protegidos Fallidos…</string>
<string name="account_history_item_received">Recibido</string> <string name="account_history_item_received">Recibido</string>
<string name="account_history_item_sending">Enviando…</string> <string name="account_history_item_sending">Enviando…</string>
<string name="account_history_item_receiving">Recibiendo…</string> <string name="account_history_item_receiving">Recibiendo…</string>

View File

@ -3,6 +3,9 @@
<string name="account_history_empty">No transaction history</string> <string name="account_history_empty">No transaction history</string>
<string name="account_history_item_sent">Sent</string> <string name="account_history_item_sent">Sent</string>
<string name="account_history_item_shielded_funds">Shielded Funds</string>
<string name="account_history_item_shielding_funds">Shielding Funds…</string>
<string name="account_history_item_shielding_failed">Shielding Failed…</string>
<string name="account_history_item_received">Received</string> <string name="account_history_item_received">Received</string>
<string name="account_history_item_sending">Sending…</string> <string name="account_history_item_sending">Sending…</string>
<string name="account_history_item_receiving">Receiving…</string> <string name="account_history_item_receiving">Receiving…</string>