[#1162] Partial transaction history item rework

- Zcash Android SDK v2.0.7 partially adopted. Proper implementaiton will be part of the Send screens rework.
- Partially addresses #1162. More related UI changes on the transaciton history item come in a follow-up PR
- `HistoryItem` composable will be reworked to several more composables as well
- Also note that the history item amount still lacks proper formatting as filed in #1047
- Closes #1236
- Closes #1288
- Closes #1253
This commit is contained in:
Honza Rychnovský 2024-03-13 09:56:49 +01:00 committed by GitHub
parent 9a929c1109
commit d076605444
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 447 additions and 155 deletions

View File

@ -189,7 +189,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-SNAPSHOT
ZCASH_SDK_VERSION=2.0.7
# Toolchain is the Java version used to build the application, which is separate from the
# Java version used to run the application.

View File

@ -6,6 +6,9 @@ import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZecSend
// TODO [#1285]: Adopt proposal API
// TODO [#1285]: https://github.com/Electric-Coin-Company/zashi-android/issues/1285
@Suppress("deprecation")
suspend fun Synchronizer.send(
spendingKey: UnifiedSpendingKey,
send: ZecSend

View File

@ -298,9 +298,10 @@ fun StyledBalance(
balanceString: String,
textStyles: Pair<TextStyle, TextStyle>,
modifier: Modifier = Modifier,
textColor: Color? = null
textColor: Color? = null,
prefix: String? = null
) {
val balanceSplit = splitBalance(balanceString)
val balanceSplit = splitBalance(balanceString, prefix)
val content =
buildAnnotatedString {
@ -338,16 +339,20 @@ fun StyledBalance(
}
}
private fun splitBalance(balance: String): Pair<String, String> {
Twig.debug { "Balance before split: $balance" }
private fun splitBalance(
balance: String,
prefix: String?
): Pair<String, String> {
Twig.debug { "Balance before split: $balance, prefix: $prefix" }
@Suppress("MAGIC_CONSTANT", "MagicNumber")
val cutPosition = balance.indexOf(MonetarySeparators.current(Locale.US).decimal) + 4
val firstPart =
balance.substring(
startIndex = 0,
endIndex = cutPosition
)
(prefix ?: "") +
balance.substring(
startIndex = 0,
endIndex = cutPosition
)
val secondPart =
balance.substring(
startIndex = cutPosition

View File

@ -46,6 +46,7 @@ data class ExtendedColors(
val radioButtonColor: Color,
val radioButtonTextColor: Color,
val historyBackgroundColor: Color,
val historySendColor: Color,
) {
@Composable
fun surfaceGradient() =

View File

@ -32,7 +32,7 @@ internal object Dark {
val textDescription = Color(0xFF777777)
val textProgress = Color(0xFF8B8A8A)
val aboutTextColor = Color.Unspecified
val aboutTextColor = Color(0xFF4E4E4E)
val screenTitleColor = Color(0xFF040404)
val welcomeAnimationColor = Color(0xFF231F20)
val complementaryColor = Color(0xFFF4B728)
@ -70,7 +70,7 @@ internal object Dark {
val overlay = Color(0x22000000)
val highlight = Color(0xFFFFD800)
val dangerous = Color(0xFFEC0008)
val dangerous = Color(0xFFFF0B0B)
val onDangerous = Color(0xFFFFFFFF)
val reference = Color(0xFFFFFFFF)
@ -81,6 +81,7 @@ internal object Dark {
val buttonShadowColor = Color(0xFFFFFFFF)
val historyBackgroundColor = Color(0xFFF6F6F6)
val historySendColor = Color(0xFFF40202)
}
internal object Light {
@ -139,7 +140,7 @@ internal object Light {
val overlay = Color(0x22000000)
val highlight = Color(0xFFFFD800)
val dangerous = Color(0xFFEC0008)
val dangerous = Color(0xFFFF0B0B)
val onDangerous = Color(0xFFFFFFFF)
val reference = Color(0xFF000000)
@ -149,6 +150,7 @@ internal object Light {
val buttonShadowColor = Color(0xFF000000)
val historyBackgroundColor = Color(0xFFF6F6F6)
val historySendColor = Color(0xFFF40202)
}
internal val DarkColorPalette =
@ -215,6 +217,7 @@ internal val DarkExtendedColorPalette =
radioButtonColor = Dark.radioButtonColor,
radioButtonTextColor = Dark.radioButtonTextColor,
historyBackgroundColor = Dark.historyBackgroundColor,
historySendColor = Dark.historySendColor,
)
internal val LightExtendedColorPalette =
@ -254,9 +257,10 @@ internal val LightExtendedColorPalette =
darkDividerColor = Light.darkDividerColor,
tabTextColor = Light.tabTextColor,
panelBackgroundColor = Light.panelBackgroundColor,
radioButtonColor = Dark.radioButtonColor,
radioButtonTextColor = Dark.radioButtonTextColor,
historyBackgroundColor = Dark.historyBackgroundColor,
radioButtonColor = Light.radioButtonColor,
radioButtonTextColor = Light.radioButtonTextColor,
historyBackgroundColor = Light.historyBackgroundColor,
historySendColor = Light.historySendColor,
)
@Suppress("CompositionLocalAllowlist")
@ -301,5 +305,6 @@ internal val LocalExtendedColors =
radioButtonColor = Color.Unspecified,
radioButtonTextColor = Color.Unspecified,
historyBackgroundColor = Color.Unspecified,
historySendColor = Color.Unspecified,
)
}

View File

@ -6,10 +6,12 @@ import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.googlefonts.Font
import androidx.compose.ui.text.googlefonts.GoogleFont
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.sp
import co.electriccoin.zcash.ui.design.R
@ -152,6 +154,17 @@ data class BalanceSingleTextStyles(
val second: TextStyle,
)
@Immutable
data class TransactionItemTextStyles(
val titleRegular: TextStyle,
val titleRunning: TextStyle,
val titleFailed: TextStyle,
val addressCollapsed: TextStyle,
val valueFirstPart: TextStyle,
val valueSecondPart: TextStyle,
val date: TextStyle,
)
@Immutable
data class ExtendedTypography(
val listItem: TextStyle,
@ -172,6 +185,8 @@ data class ExtendedTypography(
val textNavTab: TextStyle,
val referenceSmall: TextStyle,
val radioButton: TextStyle,
// Grouping transaction item text styles to a wrapper class
val transactionItemStyles: TransactionItemTextStyles,
)
@Suppress("CompositionLocalAllowlist")
@ -231,10 +246,7 @@ val LocalExtendedTypography =
)
),
addressStyle =
SecondaryTypography.bodyLarge.copy(
// TODO [#1032]: Addresses can be shown with "×" symbols
// TODO [#1032]: https://github.com/Electric-Coin-Company/zashi-android/issues/1032
),
SecondaryTypography.bodyLarge.copy(),
aboutText =
PrimaryTypography.bodyLarge.copy(
fontSize = 14.sp,
@ -286,6 +298,42 @@ val LocalExtendedTypography =
PrimaryTypography.bodySmall.copy(
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
)
),
transactionItemStyles =
TransactionItemTextStyles(
titleRegular =
PrimaryTypography.bodySmall.copy(
fontSize = 13.sp,
fontWeight = FontWeight.Bold
),
titleRunning =
PrimaryTypography.bodySmall.copy(
fontSize = 13.sp,
fontWeight = FontWeight.ExtraBold,
fontStyle = FontStyle.Italic
),
titleFailed =
PrimaryTypography.bodySmall.copy(
fontSize = 13.sp,
fontWeight = FontWeight.ExtraBold,
textDecoration = TextDecoration.LineThrough
),
addressCollapsed =
SecondaryTypography.bodySmall.copy(
fontSize = 13.sp
),
valueFirstPart =
PrimaryTypography.bodySmall.copy(
fontSize = 13.sp
),
valueSecondPart =
PrimaryTypography.bodySmall.copy(
fontSize = 8.sp
),
date =
PrimaryTypography.bodySmall.copy(
fontSize = 13.sp
),
),
)
}

View File

@ -7,8 +7,10 @@ import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
@ -80,6 +82,13 @@ internal class MockSynchronizer : CloseableSynchronizer {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
override suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): Flow<TransactionSubmitResult> {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} yet.")
}
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
@ -120,6 +129,24 @@ internal class MockSynchronizer : CloseableSynchronizer {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
override suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: String,
transparentReceiver: String?
): Proposal? {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} yet.")
}
override suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: String
): Proposal {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} yet.")
}
override suspend fun quickRewind() {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
@ -135,6 +162,13 @@ internal class MockSynchronizer : CloseableSynchronizer {
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
}
@Deprecated(
"Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)"
)
)
override suspend fun sendToAddress(
usk: UnifiedSpendingKey,
amount: Zatoshi,
@ -144,6 +178,13 @@ internal class MockSynchronizer : CloseableSynchronizer {
return 1
}
@Deprecated(
"Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }"
)
)
override suspend fun shieldFunds(
usk: UnifiedSpendingKey,
memo: String

View File

@ -1,22 +1,24 @@
package co.electriccoin.zcash.ui.screen.account.history.fixture
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.TransactionRecipient
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
import co.electriccoin.zcash.ui.screen.account.state.TransactionOverviewExt
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal object TransactionHistorySyncStateFixture {
val TRANSACTIONS =
persistentListOf(
TransactionOverviewFixture.new(),
TransactionOverviewFixture.new(),
TransactionOverviewFixture.new()
TransactionOverviewExt(TransactionOverviewFixture.new(), TransactionRecipient.Account(Account.DEFAULT)),
TransactionOverviewExt(TransactionOverviewFixture.new(), TransactionRecipient.Account(Account(1))),
TransactionOverviewExt(TransactionOverviewFixture.new(), null),
)
val STATE = TransactionHistorySyncState.Syncing(TRANSACTIONS)
fun new(
transactions: ImmutableList<TransactionOverview> = TRANSACTIONS,
transactions: ImmutableList<TransactionOverviewExt> = TRANSACTIONS,
state: TransactionHistorySyncState = STATE
) = when (state) {
is TransactionHistorySyncState.Syncing -> {

View File

@ -110,7 +110,7 @@ class HistoryViewTest {
assertEquals(0, testSetup.getOnItemClickCount())
composeTestRule.onAllNodesWithTag(HistoryTag.TRANSACTION_ITEM, useUnmergedTree = true).also {
composeTestRule.onAllNodesWithTag(HistoryTag.TRANSACTION_ITEM_TITLE, useUnmergedTree = true).also {
it.assertCountEquals(TransactionHistorySyncStateFixture.TRANSACTIONS.size)
TransactionHistorySyncStateFixture.TRANSACTIONS.forEachIndexed { index, _ ->

View File

@ -35,6 +35,7 @@ import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
import co.electriccoin.zcash.ui.screen.account.state.TransactionOverviewExt
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -48,6 +49,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@ -183,13 +185,22 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val transactionHistoryState =
synchronizer
.filterNotNull()
.flatMapLatest {
it.transactions
.combine(it.status) { transactions: List<TransactionOverview>, status: Synchronizer.Status ->
.flatMapLatest { synchronizer ->
synchronizer.transactions
.combine(synchronizer.status) {
transactions: List<TransactionOverview>, status: Synchronizer.Status ->
val enhancedTransactions =
transactions.map {
if (it.isSentTransaction) {
TransactionOverviewExt(it, synchronizer.getRecipients(it).firstOrNull())
} else {
TransactionOverviewExt(it, null)
}
}
if (status.isSyncing()) {
TransactionHistorySyncState.Syncing(transactions.toPersistentList())
TransactionHistorySyncState.Syncing(enhancedTransactions.toPersistentList())
} else {
TransactionHistorySyncState.Done(transactions.toPersistentList())
TransactionHistorySyncState.Done(enhancedTransactions.toPersistentList())
}
}
}

View File

@ -50,14 +50,14 @@ internal fun WrapAccount(
transactionState = transactionHistoryState,
onItemClick = { tx ->
Twig.debug { "Transaction item clicked - querying memos..." }
val memos = synchronizer?.getMemos(tx)
val memos = synchronizer?.getMemos(tx.overview)
scope.launch {
memos?.toList()?.let {
val merged = it.joinToString().ifEmpty { "-" }
Twig.info { "Transaction memos: count: ${it.size}, contains: $merged" }
ClipboardManagerUtil.copyToClipboard(
activity.applicationContext,
activity.getString(R.string.history_item_clipboard_tag),
activity.getString(R.string.account_history_item_clipboard_tag),
merged
)
}
@ -67,7 +67,7 @@ internal fun WrapAccount(
Twig.debug { "Transaction ID clicked: $txId" }
ClipboardManagerUtil.copyToClipboard(
activity.applicationContext,
activity.getString(R.string.history_id_clipboard_tag),
activity.getString(R.string.account_history_id_clipboard_tag),
txId
)
},

View File

@ -5,7 +5,7 @@ package co.electriccoin.zcash.ui.screen.account
*/
object HistoryTag {
const val TRANSACTION_LIST = "transaction_list"
const val TRANSACTION_ITEM = "transaction_item"
const val TRANSACTION_ITEM_TITLE = "transaction_item_title"
const val TRANSACTION_ID = "transaction_id"
const val PROGRESS = "progress_bar"
}

View File

@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.screen.account.state
import cash.z.ecc.android.sdk.model.TransactionOverview
import kotlinx.collections.immutable.ImmutableList
sealed class TransactionHistorySyncState {
@ -8,13 +7,13 @@ sealed class TransactionHistorySyncState {
override fun toString() = "Loading" // NON-NLS
}
data class Syncing(val transactions: ImmutableList<TransactionOverview>) : TransactionHistorySyncState() {
data class Syncing(val transactions: ImmutableList<TransactionOverviewExt>) : TransactionHistorySyncState() {
fun hasNoTransactions(): Boolean {
return transactions.isEmpty()
}
}
data class Done(val transactions: ImmutableList<TransactionOverview>) : TransactionHistorySyncState() {
data class Done(val transactions: ImmutableList<TransactionOverviewExt>) : TransactionHistorySyncState() {
fun hasNoTransactions(): Boolean {
return transactions.isEmpty()
}

View File

@ -0,0 +1,9 @@
package co.electriccoin.zcash.ui.screen.account.state
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient
data class TransactionOverviewExt(
val overview: TransactionOverview,
val recipient: TransactionRecipient?
)

View File

@ -17,7 +17,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
@ -30,6 +29,7 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.account.AccountTag
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
import co.electriccoin.zcash.ui.screen.account.state.TransactionOverviewExt
import kotlinx.collections.immutable.persistentListOf
@Preview("Account No History")
@ -64,9 +64,18 @@ private fun HistoryListComposablePreview() {
TransactionHistorySyncState.Syncing(
@Suppress("MagicNumber")
persistentListOf(
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
null
),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
null
),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
null
),
)
),
onItemClick = {},
@ -82,7 +91,7 @@ fun Account(
goBalances: () -> Unit,
goSettings: () -> Unit,
isKeepScreenOnWhileSyncing: Boolean?,
onItemClick: (TransactionOverview) -> Unit,
onItemClick: (TransactionOverviewExt) -> Unit,
onTransactionIdClick: (String) -> Unit,
transactionState: TransactionHistorySyncState,
walletSnapshot: WalletSnapshot,
@ -131,7 +140,7 @@ private fun AccountMainContent(
walletSnapshot: WalletSnapshot,
isKeepScreenOnWhileSyncing: Boolean?,
goBalances: () -> Unit,
onItemClick: (TransactionOverview) -> Unit,
onItemClick: (TransactionOverviewExt) -> Unit,
onTransactionIdClick: (String) -> Unit,
transactionState: TransactionHistorySyncState,
modifier: Modifier = Modifier

View File

@ -1,5 +1,6 @@
package co.electriccoin.zcash.ui.screen.account.view
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -14,39 +15,43 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.outlined.ArrowCircleDown
import androidx.compose.material.icons.outlined.ArrowCircleUp
import androidx.compose.material.icons.twotone.ArrowCircleDown
import androidx.compose.material.icons.twotone.ArrowCircleUp
import androidx.compose.material3.Divider
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionState
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.ui.R
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.StyledBalance
import co.electriccoin.zcash.ui.design.component.Tiny
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.account.HistoryTag
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
import co.electriccoin.zcash.ui.screen.account.state.TransactionOverviewExt
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.text.DateFormat
@ -77,9 +82,18 @@ private fun ComposableHistoryListPreview() {
TransactionHistorySyncState.Syncing(
@Suppress("MagicNumber")
persistentListOf(
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
null
),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
null
),
TransactionOverviewExt(
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
null
),
)
),
onItemClick = {},
@ -93,7 +107,9 @@ val dateFormat: DateFormat by lazy {
SimpleDateFormat.getDateTimeInstance(
SimpleDateFormat.MEDIUM,
SimpleDateFormat.SHORT,
Locale.getDefault()
// TODO [#1171]: Remove default MonetarySeparators locale
// TODO [#1171]: https://github.com/Electric-Coin-Company/zashi-android/issues/1171
Locale.US
)
}
@ -101,7 +117,7 @@ val dateFormat: DateFormat by lazy {
@Suppress("LongMethod")
fun HistoryContainer(
transactionState: TransactionHistorySyncState,
onItemClick: (TransactionOverview) -> Unit,
onItemClick: (TransactionOverviewExt) -> Unit,
onTransactionIdClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
@ -143,141 +159,223 @@ fun HistoryContainer(
@Composable
private fun HistoryList(
transactions: ImmutableList<TransactionOverview>,
onItemClick: (TransactionOverview) -> Unit,
transactions: ImmutableList<TransactionOverviewExt>,
onItemClick: (TransactionOverviewExt) -> Unit,
onTransactionIdClick: (String) -> Unit
) {
val currency = ZcashCurrency.getLocalizedName(LocalContext.current)
if (transactions.isEmpty()) {
Column {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingXlarge))
LazyColumn(
modifier = Modifier.testTag(HistoryTag.TRANSACTION_LIST)
) {
itemsIndexed(transactions) { _, item ->
HistoryItem(
transaction = item,
currency = currency,
onItemClick = onItemClick,
onIdClick = onTransactionIdClick,
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
text = stringResource(id = R.string.account_history_empty),
style = ZcashTheme.extendedTypography.transactionItemStyles.titleRegular,
color = ZcashTheme.colors.textCommon,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
} else {
LazyColumn(
modifier = Modifier.testTag(HistoryTag.TRANSACTION_LIST)
) {
itemsIndexed(transactions) { _, item ->
HistoryItem(
transaction = item,
onItemClick = onItemClick,
onIdClick = onTransactionIdClick,
)
Divider(
color = ZcashTheme.colors.dividerColor,
thickness = DividerDefaults.Thickness,
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingDefault)
)
Divider(
color = ZcashTheme.colors.dividerColor,
thickness = DividerDefaults.Thickness,
modifier = Modifier.padding(horizontal = ZcashTheme.dimens.spacingDefault)
)
}
}
}
}
private enum class ItemExpandedState {
COLLAPSED,
EXPANDED,
EXPANDED_ADDRESS,
EXPANDED_ID
}
const val ADDRESS_IN_TITLE_WIDTH_RATIO = 0.5f
@Composable
@Suppress("LongMethod")
@Suppress("LongMethod", "CyclomaticComplexMethod")
fun HistoryItem(
transaction: TransactionOverview,
currency: String,
onItemClick: (TransactionOverview) -> Unit,
transaction: TransactionOverviewExt,
onItemClick: (TransactionOverviewExt) -> Unit,
onIdClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
val transactionTypeText: String
val transactionTypeIcon: ImageVector
when (transaction.getExtendedState()) {
val typeText: String
val textColor: Color
val typeIcon: ImageVector
val textStyle: TextStyle
when (transaction.overview.getExtendedState()) {
TransactionExtendedState.SENT -> {
transactionTypeText = stringResource(id = R.string.history_item_sent)
transactionTypeIcon = Icons.TwoTone.ArrowCircleUp
typeText = stringResource(id = R.string.account_history_item_sent)
typeIcon = ImageVector.vectorResource(R.drawable.trx_send_icon)
textColor = MaterialTheme.colorScheme.onBackground
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRegular
}
TransactionExtendedState.SENDING -> {
transactionTypeText = stringResource(id = R.string.history_item_sending)
transactionTypeIcon = Icons.Outlined.ArrowCircleUp
typeText = stringResource(id = R.string.account_history_item_sending)
typeIcon = ImageVector.vectorResource(R.drawable.trx_send_icon)
textColor = ZcashTheme.colors.textDescription
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRunning
}
TransactionExtendedState.SEND_FAILED -> {
typeText = stringResource(id = R.string.account_history_item_send_failed)
typeIcon = ImageVector.vectorResource(R.drawable.trx_send_icon)
textColor = ZcashTheme.colors.dangerous
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleFailed
}
TransactionExtendedState.RECEIVED -> {
transactionTypeText = stringResource(id = R.string.history_item_received)
transactionTypeIcon = Icons.TwoTone.ArrowCircleDown
typeText = stringResource(id = R.string.account_history_item_received)
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
textColor = MaterialTheme.colorScheme.onBackground
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRegular
}
TransactionExtendedState.RECEIVING -> {
transactionTypeText = stringResource(id = R.string.history_item_receiving)
transactionTypeIcon = Icons.Outlined.ArrowCircleDown
typeText = stringResource(id = R.string.account_history_item_receiving)
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
textColor = ZcashTheme.colors.textDescription
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRunning
}
TransactionExtendedState.EXPIRED -> {
transactionTypeText = stringResource(id = R.string.history_item_expired)
transactionTypeIcon = Icons.Filled.Cancel
TransactionExtendedState.RECEIVE_FAILED -> {
typeText = stringResource(id = R.string.account_history_item_receive_failed)
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
textColor = ZcashTheme.colors.dangerous
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleFailed
}
}
var expandedState: ItemExpandedState by rememberSaveable {
mutableStateOf(ItemExpandedState.COLLAPSED)
}
Row(
modifier =
modifier
.then(
Modifier
.fillMaxWidth()
.clickable { onItemClick(transaction) }
.background(color = ZcashTheme.colors.historyBackgroundColor)
.padding(all = ZcashTheme.dimens.spacingDefault)
),
verticalAlignment = Alignment.CenterVertically
.clickable {
if (expandedState == ItemExpandedState.COLLAPSED) {
expandedState = ItemExpandedState.EXPANDED
}
onItemClick(transaction)
}
.padding(all = ZcashTheme.dimens.spacingLarge)
.animateContentSize()
)
) {
Image(
imageVector = transactionTypeIcon,
contentDescription = transactionTypeText,
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingTiny)
imageVector = typeIcon,
contentDescription = typeText,
)
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
Column(modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingDefault))
Column {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Body(
text = transactionTypeText,
color = Color.Black,
modifier = Modifier.testTag(HistoryTag.TRANSACTION_ITEM)
)
Text(
text = typeText,
style = textStyle,
color = textColor,
modifier = Modifier.testTag(HistoryTag.TRANSACTION_ITEM_TITLE)
)
val dateString =
transaction.minedHeight?.let {
transaction.blockTimeEpochSeconds?.let { blockTimeEpochSeconds ->
// * 1000 to covert to millis
@Suppress("MagicNumber")
dateFormat.format(blockTimeEpochSeconds.times(1000L))
} ?: stringResource(id = R.string.history_item_date_not_available)
} ?: stringResource(id = R.string.history_item_date_not_available)
// For now, use the same label for the above missing transaction date
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
Body(
text = dateString,
if (transaction.recipient != null && transaction.recipient is TransactionRecipient.Address) {
Text(
text = transaction.recipient.addressValue,
style = ZcashTheme.extendedTypography.transactionItemStyles.addressCollapsed,
color = ZcashTheme.colors.textDescription,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
Modifier
.fillMaxWidth(ADDRESS_IN_TITLE_WIDTH_RATIO)
)
} else {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.trx_shielded),
contentDescription = stringResource(id = R.string.account_history_item_shielded)
)
}
Column {
Row(modifier = Modifier.align(alignment = Alignment.End)) {
val zecString =
if (transaction.isSentTransaction) {
"-${transaction.netValue.toZecString()}"
} else {
transaction.netValue.toZecString()
}
Body(text = zecString)
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
Spacer(modifier = Modifier.weight(1f))
Body(text = currency)
}
}
StyledBalance(
balanceString = transaction.overview.netValue.toZecString(),
textStyles =
Pair(
first = ZcashTheme.extendedTypography.transactionItemStyles.valueFirstPart,
second = ZcashTheme.extendedTypography.transactionItemStyles.valueSecondPart
),
textColor =
if (transaction.overview.isSentTransaction) {
ZcashTheme.colors.historySendColor
} else {
ZcashTheme.colors.textCommon
},
prefix =
if (transaction.overview.isSentTransaction) {
stringResource(id = R.string.account_history_item_sent_prefix)
} else {
stringResource(id = R.string.account_history_item_received_prefix)
}
)
}
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
val txId = transaction.txIdString()
Tiny(
text = txId,
modifier =
Modifier
.clickable { onIdClick(txId) }
.testTag(HistoryTag.TRANSACTION_ID)
val dateString =
transaction.overview.minedHeight?.let {
transaction.overview.blockTimeEpochSeconds?.let { blockTimeEpochSeconds ->
// * 1000 to covert to millis
@Suppress("MagicNumber")
dateFormat.format(blockTimeEpochSeconds.times(1000))
} ?: "-"
} ?: "-"
// For now, use the same label for the above missing transaction date
Text(
text = dateString,
style = ZcashTheme.extendedTypography.transactionItemStyles.date,
color = ZcashTheme.colors.textDescription,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (expandedState >= ItemExpandedState.EXPANDED) {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
val txId = transaction.overview.txIdString()
Tiny(
text = txId,
modifier =
Modifier
.clickable { onIdClick(txId) }
.testTag(HistoryTag.TRANSACTION_ID)
)
}
}
}
}
@ -285,15 +383,20 @@ fun HistoryItem(
enum class TransactionExtendedState {
SENT,
SENDING,
SEND_FAILED,
RECEIVED,
RECEIVING,
EXPIRED
RECEIVE_FAILED,
}
private fun TransactionOverview.getExtendedState(): TransactionExtendedState {
return when (transactionState) {
TransactionState.Expired -> {
TransactionExtendedState.EXPIRED
if (isSentTransaction) {
TransactionExtendedState.SEND_FAILED
} else {
TransactionExtendedState.RECEIVE_FAILED
}
}
TransactionState.Confirmed -> {
if (isSentTransaction) {

View File

@ -114,7 +114,12 @@ internal fun WrapBalances(
Twig.debug { "Shielding transparent funds" }
// Using empty string for memo to clear the default memo prefix value defined in the SDK
runCatching { synchronizer.shieldFunds(spendingKey, "") }
runCatching {
// TODO [#1285]: Adopt proposal API
// TODO [#1285]: https://github.com/Electric-Coin-Company/zashi-android/issues/1285
@Suppress("deprecation")
synchronizer.shieldFunds(spendingKey, "")
}
.onSuccess {
Twig.info { "Shielding transaction id:$it submitted successfully" }
setShieldState(ShieldState.None)

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="12dp"
android:viewportWidth="18"
android:viewportHeight="12">
<group>
<clip-path
android:pathData="M0,0h17.55v11.1h-17.55z"/>
<path
android:pathData="M17.39,0.09C17.32,0.03 17.22,0 17.13,0H17.04L0.32,4.29C0.13,4.34 0,4.51 0,4.71C0,4.91 0.14,5.07 0.33,5.12L8.59,7.07L16.95,11.06C17.01,11.09 17.07,11.1 17.13,11.1C17.21,11.1 17.29,11.08 17.36,11.03C17.48,10.95 17.56,10.82 17.56,10.67V0.43C17.56,0.3 17.5,0.18 17.4,0.09H17.39ZM16.7,4.73V10L5.68,4.73H16.71H16.7ZM5.33,3.88L16.7,0.97V3.88H5.33ZM2.39,4.73H4.17L5.97,5.58L2.39,4.73Z"
android:fillColor="#000000"/>
</group>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="16dp"
android:viewportWidth="20"
android:viewportHeight="16">
<group>
<clip-path
android:pathData="M0,0h20v16h-20z"/>
<path
android:pathData="M19.92,0.07C19.81,-0.03 19.5,-0 19.31,0.06C13.04,2.2 6.77,4.34 0.5,6.5C0.31,6.56 0.17,6.75 0,6.88C0.13,7.04 0.24,7.25 0.41,7.35C1.57,8.05 2.76,8.72 3.92,9.43C4.15,9.57 4.37,9.85 4.44,10.11C4.93,11.9 5.38,13.7 5.86,15.49C5.91,15.68 6.05,15.96 6.2,15.99C6.35,16.02 6.6,15.85 6.74,15.7C7.68,14.75 8.62,13.79 9.53,12.82C9.79,12.54 9.99,12.48 10.35,12.66C11.64,13.32 12.95,13.93 14.25,14.56C14.39,14.63 14.53,14.69 14.77,14.79C14.89,14.6 15.04,14.43 15.12,14.23C16.74,9.71 18.35,5.18 19.96,0.65C20.02,0.47 20.04,0.16 19.93,0.06L19.92,0.07ZM4.26,8.81C3.28,8.21 2.28,7.64 1.2,7C6.3,5.25 11.28,3.55 16.26,1.84L16.3,1.92C16.16,2.01 16.03,2.1 15.89,2.18C12.27,4.38 8.66,6.57 5.04,8.78C4.76,8.95 4.55,8.99 4.26,8.8V8.81ZM6.71,10.97C6.53,11.87 6.39,12.77 6.12,13.68C6.02,13.3 5.92,12.93 5.82,12.55C5.6,11.69 5.38,10.84 5.15,9.98C5.09,9.76 5.06,9.58 5.32,9.43C8.56,7.48 11.79,5.51 15.03,3.55C15.07,3.53 15.12,3.53 15.3,3.49C14.16,4.44 13.13,5.3 12.09,6.16C11.63,6.54 8.6,8.99 8.14,9.38C8.07,9.44 8,9.5 7.95,9.57C7.62,9.81 7.3,10.08 7.01,10.37C6.85,10.52 6.75,10.77 6.71,10.98V10.97ZM6.81,14.61C7.01,13.42 7.19,12.38 7.38,11.25C8,11.55 8.58,11.83 9.2,12.13C8.41,12.95 7.65,13.73 6.8,14.61H6.81ZM14.48,13.9C12.24,12.81 10.04,11.75 7.77,10.65C8.09,10.38 8.39,10.18 8.62,9.92C11.01,7.93 15.96,3.88 18.35,1.89C18.49,1.77 18.63,1.66 18.86,1.6C17.41,5.68 15.95,9.77 14.48,13.9Z"
android:fillColor="#000000"/>
</group>
</vector>

View File

@ -1,11 +1,17 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="history_item_clipboard_tag">Transaction memo</string>
<string name="history_id_clipboard_tag">Transaction ID</string>
<string name="history_item_sent">Sent</string>
<string name="history_item_received">Received</string>
<string name="history_item_sending">Sending</string>
<string name="history_item_receiving">Receiving</string>
<string name="history_item_expired">Expired</string>
<string name="history_item_date_not_available">Date not available</string>
<string name="account_history_empty">No transaction history</string>
<string name="account_history_item_sent">Sent</string>
<string name="account_history_item_received">Received</string>
<string name="account_history_item_sending">Sending…</string>
<string name="account_history_item_receiving">Receiving…</string>
<string name="account_history_item_receive_failed">Receive failed</string>
<string name="account_history_item_send_failed">Send failed</string>
<string name="account_history_item_shielded">Shielded transaction</string>
<string name="account_history_item_sent_prefix">-</string>
<string name="account_history_item_received_prefix">+</string>
<string name="account_history_item_clipboard_tag">Transaction memo</string>
<string name="account_history_id_clipboard_tag">Transaction ID</string>
</resources>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="14dp"
android:viewportWidth="17"
android:viewportHeight="14">
<group>
<clip-path
android:pathData="M17,0l-17,0l-0,13.909l17,0z"/>
<path
android:pathData="M4.481,3.052L8.415,1.794C8.472,1.772 8.528,1.772 8.585,1.794L12.519,3.052C12.697,3.108 12.728,3.151 12.728,3.352V7.967C12.728,8.568 12.5,9.182 12.051,9.794C11.707,10.261 11.232,10.73 10.639,11.188C9.643,11.957 8.661,12.429 8.619,12.449C8.54,12.488 8.459,12.488 8.379,12.449C8.338,12.429 7.357,11.957 6.359,11.188C5.766,10.73 5.291,10.26 4.948,9.794C4.498,9.182 4.27,8.568 4.27,7.967V3.352C4.28,3.143 4.337,3.114 4.479,3.052H4.481Z"
android:fillColor="#231F20"/>
<path
android:pathData="M17,13.909V0H13.542V0.96H15.715V12.948H13.542V13.908H17V13.909Z"
android:fillColor="#231F20"/>
<path
android:pathData="M0,0V13.909H3.458V12.95H1.285V0.96H3.458V0H0Z"
android:fillColor="#231F20"/>
</group>
</vector>

View File

@ -10,7 +10,7 @@
<string name="balances_transparent_balance_help_close">I got it!</string>
<string name="balances_transparent_help_content_description">Show help</string>
<string name="balances_transparent_balance_shield">Shield and consolidate funds</string>
<string name="balances_transparent_balance_fee">(Typical fee &lt; <xliff:g id="fee_amount" example="0.001">%1$s
<string name="balances_transparent_balance_fee">(Typical Fee &lt; <xliff:g id="fee_amount" example="0.001">%1$s
</xliff:g>)</string>
<string name="balances_status_syncing" formatted="true">Syncing…</string>

View File

@ -17,7 +17,7 @@
<xliff:g id="max_bytes" example="500">%2$s</xliff:g>
</string>
<string name="send_create">Review</string>
<string name="send_fee">(Typical fee &lt; <xliff:g id="fee_amount" example="0.001">%1$s</xliff:g>)</string>
<string name="send_fee">(Typical Fee &lt; <xliff:g id="fee_amount" example="0.001">%1$s</xliff:g>)</string>
<string name="send_confirmation_amount_and_address_format" formatted="true">Send <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g>?</string>
<string name="send_confirmation_memo_format" formatted="true">Memo: <xliff:g id="memo" example="for Veronika">%1$s</xliff:g></string>