[#1162] Expandable transaction history item
Although this brings many changes to the transaction history item UI, it only partly solves Expandable transaction history item #1162 Follow-up PR will close it
This commit is contained in:
parent
c99c2907b1
commit
4ae3fde690
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AccountBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -30,6 +31,7 @@ import androidx.compose.ui.text.style.TextDecoration
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
@ -227,16 +229,41 @@ fun Tiny(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ListHeader(
|
||||
@Suppress("LongParameterList")
|
||||
fun TextWithIcon(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
imageContentDescription: String? = null,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
color: Color = MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = ZcashTheme.extendedTypography.listItem,
|
||||
color = ZcashTheme.colors.onBackgroundHeader,
|
||||
modifier = modifier
|
||||
)
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = imageContentDescription
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(3.dp))
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
textAlign = textAlign,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
|
@ -47,6 +47,7 @@ data class Dimens(
|
|||
val layoutStroke: Dp,
|
||||
val regularRippleEffectCorner: Dp,
|
||||
val smallRippleEffectCorner: Dp,
|
||||
val tinyRippleEffectCorner: Dp,
|
||||
// Screen custom spacings:
|
||||
val inScreenZcashLogoHeight: Dp,
|
||||
val inScreenZcashLogoWidth: Dp,
|
||||
|
@ -87,6 +88,7 @@ private val defaultDimens =
|
|||
divider = 1.dp,
|
||||
regularRippleEffectCorner = 28.dp,
|
||||
smallRippleEffectCorner = 10.dp,
|
||||
tinyRippleEffectCorner = 8.dp,
|
||||
inScreenZcashLogoHeight = 100.dp,
|
||||
inScreenZcashLogoWidth = 60.dp,
|
||||
inScreenZcashTextLogoHeight = 30.dp,
|
||||
|
|
|
@ -20,6 +20,7 @@ data class ExtendedColors(
|
|||
val linearProgressBarBackground: Color,
|
||||
val chipIndex: Color,
|
||||
val textCommon: Color,
|
||||
val textMedium: Color,
|
||||
val textDisabled: Color,
|
||||
val textFieldHint: Color,
|
||||
val textFieldError: Color,
|
||||
|
|
|
@ -24,6 +24,7 @@ internal object Dark {
|
|||
val textSecondaryButton = Color(0xFF000000)
|
||||
val textTertiaryButton = Color.White
|
||||
val textCommon = Color(0xFFFFFFFF)
|
||||
val textMedium = Color(0xFF353535)
|
||||
val textDisabled = Color(0xFFB7B7B7)
|
||||
val textChipIndex = Color(0xFFFFB900)
|
||||
val textFieldFrame = Color(0xFF231F20)
|
||||
|
@ -43,18 +44,8 @@ internal object Dark {
|
|||
val panelBackgroundColor = Color(0xFFEAEAEA)
|
||||
|
||||
val primaryButton = Color(0xFFFFFFFF)
|
||||
val primaryButtonPressed = Color(0xFFFFFFFF)
|
||||
val primaryButtonDisabled = Color(0xFFFFFFFF)
|
||||
|
||||
val secondaryButton = Color(0xFFFFFFFF)
|
||||
val secondaryButtonPressed = Color(0xFFFFFFFF)
|
||||
val secondaryButtonDisabled = Color(0xFFFFFFFF)
|
||||
|
||||
val tertiaryButton = Color.Transparent
|
||||
val tertiaryButtonPressed = Color(0xFFFFFFFF)
|
||||
|
||||
val navigationButton = Color(0xFFFFFFFF)
|
||||
val navigationButtonPressed = Color(0xFFFFFFFF)
|
||||
|
||||
val radioButtonColor = Color(0xFF070707)
|
||||
val radioButtonTextColor = Color(0xFF4E4E4E)
|
||||
|
@ -94,6 +85,7 @@ internal object Light {
|
|||
val textSecondaryButton = Color(0xFF000000)
|
||||
val textTertiaryButton = Color(0xFF000000)
|
||||
val textCommon = Color(0xFF000000)
|
||||
val textMedium = Color(0xFF353535)
|
||||
val textDisabled = Color(0xFFB7B7B7)
|
||||
val textFieldFrame = Color(0xFF231F20)
|
||||
val textFieldError = Color(0xFFCD0002)
|
||||
|
@ -113,18 +105,8 @@ internal object Light {
|
|||
val panelBackgroundColor = Color(0xFFEAEAEA)
|
||||
|
||||
val primaryButton = Color(0xFF000000)
|
||||
val primaryButtonPressed = Color(0xFF000000)
|
||||
val primaryButtonDisabled = Color(0xFF000000)
|
||||
|
||||
val secondaryButton = Color(0xFFFFFFFF)
|
||||
val secondaryButtonPressed = Color(0xFFFFFFFF)
|
||||
val secondaryButtonDisabled = Color(0xFFFFFFFF)
|
||||
|
||||
val tertiaryButton = Color.Transparent
|
||||
val tertiaryButtonPressed = Color(0xFFFFFFFF)
|
||||
|
||||
val navigationButton = Color(0xFFFFFFFF)
|
||||
val navigationButtonPressed = Color(0xFFFFFFFF)
|
||||
|
||||
val radioButtonColor = Color(0xFF070707)
|
||||
val radioButtonTextColor = Color(0xFF4E4E4E)
|
||||
|
@ -191,6 +173,7 @@ internal val DarkExtendedColorPalette =
|
|||
linearProgressBarBackground = Dark.linearProgressBarBackground,
|
||||
chipIndex = Dark.textChipIndex,
|
||||
textCommon = Dark.textCommon,
|
||||
textMedium = Dark.textMedium,
|
||||
textDisabled = Dark.textDisabled,
|
||||
textFieldFrame = Dark.textFieldFrame,
|
||||
textFieldError = Dark.textFieldError,
|
||||
|
@ -234,6 +217,7 @@ internal val LightExtendedColorPalette =
|
|||
linearProgressBarBackground = Light.linearProgressBarBackground,
|
||||
chipIndex = Light.textChipIndex,
|
||||
textCommon = Light.textCommon,
|
||||
textMedium = Dark.textMedium,
|
||||
textDisabled = Light.textDisabled,
|
||||
textFieldFrame = Light.textFieldFrame,
|
||||
textFieldError = Light.textFieldError,
|
||||
|
@ -279,6 +263,7 @@ internal val LocalExtendedColors =
|
|||
linearProgressBarBackground = Color.Unspecified,
|
||||
chipIndex = Color.Unspecified,
|
||||
textCommon = Color.Unspecified,
|
||||
textMedium = Color.Unspecified,
|
||||
textDisabled = Color.Unspecified,
|
||||
textFieldHint = Color.Unspecified,
|
||||
textFieldError = Color.Unspecified,
|
||||
|
|
|
@ -162,12 +162,13 @@ data class TransactionItemTextStyles(
|
|||
val addressCollapsed: TextStyle,
|
||||
val valueFirstPart: TextStyle,
|
||||
val valueSecondPart: TextStyle,
|
||||
val date: TextStyle,
|
||||
val content: TextStyle,
|
||||
val contentMedium: TextStyle,
|
||||
val contentUnderline: TextStyle,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class ExtendedTypography(
|
||||
val listItem: TextStyle,
|
||||
// Grouping balances text styles to a wrapper class for BalanceWidget
|
||||
val balanceWidgetStyles: BalanceWidgetTextStyles,
|
||||
// Grouping balances text styles to a wrapper class for single balance use case
|
||||
|
@ -202,10 +203,6 @@ val LocalTypographies =
|
|||
val LocalExtendedTypography =
|
||||
staticCompositionLocalOf {
|
||||
ExtendedTypography(
|
||||
listItem =
|
||||
PrimaryTypography.bodyLarge.copy(
|
||||
fontSize = 24.sp
|
||||
),
|
||||
// Note: the order here matters, be careful when reordering
|
||||
balanceWidgetStyles =
|
||||
BalanceWidgetTextStyles(
|
||||
|
@ -330,10 +327,21 @@ val LocalExtendedTypography =
|
|||
PrimaryTypography.bodySmall.copy(
|
||||
fontSize = 8.sp
|
||||
),
|
||||
date =
|
||||
content =
|
||||
PrimaryTypography.bodySmall.copy(
|
||||
fontSize = 13.sp
|
||||
),
|
||||
contentMedium =
|
||||
PrimaryTypography.bodySmall.copy(
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
),
|
||||
contentUnderline =
|
||||
PrimaryTypography.bodySmall.copy(
|
||||
fontSize = 13.sp,
|
||||
fontStyle = FontStyle.Italic,
|
||||
textDecoration = TextDecoration.Underline
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,12 @@
|
|||
package co.electriccoin.zcash.ui.screen.account.history.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||
import co.electriccoin.zcash.ui.screen.account.model.HistoryItemExpandableState
|
||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object TransactionHistoryUiStateFixture {
|
||||
val TRANSACTIONS =
|
||||
persistentListOf(
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(),
|
||||
TransactionRecipient.Account(Account.DEFAULT),
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(),
|
||||
TransactionRecipient.Account(Account(1)),
|
||||
HistoryItemExpandableState.EXPANDED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(),
|
||||
null,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
)
|
||||
val TRANSACTIONS = TransactionsFixture.new()
|
||||
val STATE = TransactionUiState.Prepared(TRANSACTIONS)
|
||||
|
||||
fun new(
|
||||
|
|
|
@ -140,12 +140,10 @@ class HistoryViewTest {
|
|||
assertEquals(TransactionHistorySyncStateFixture.TRANSACTIONS.size, testSetup.getOnItemIdClickCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(
|
||||
transactionHistorySyncState: TransactionUiState = TransactionHistoryUiStateFixture.new()
|
||||
): HistoryTestSetup {
|
||||
private fun newTestSetup(state: TransactionUiState = TransactionHistoryUiStateFixture.new()): HistoryTestSetup {
|
||||
return HistoryTestSetup(
|
||||
composeTestRule = composeTestRule,
|
||||
initialHistoryUiState = transactionHistorySyncState
|
||||
initialHistoryUiState = state
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,27 @@
|
|||
|
||||
package co.electriccoin.zcash.ui.screen.account
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
import co.electriccoin.zcash.ui.R
|
||||
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.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.account.view.Account
|
||||
import co.electriccoin.zcash.ui.screen.account.view.TransactionItemAction
|
||||
import co.electriccoin.zcash.ui.screen.account.view.TrxItemAction
|
||||
import co.electriccoin.zcash.ui.screen.account.viewmodel.TransactionHistoryViewModel
|
||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
@Composable
|
||||
internal fun WrapAccount(
|
||||
|
@ -39,15 +44,43 @@ internal fun WrapAccount(
|
|||
|
||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||
|
||||
val transactionHistoryState = walletViewModel.transactionHistoryState.collectAsStateWithLifecycle().value
|
||||
|
||||
val transactionsUiState = transactionHistoryViewModel.transactionUiState.collectAsStateWithLifecycle().value
|
||||
|
||||
Twig.info { "Current transaction history state: $transactionsUiState" }
|
||||
walletViewModel.transactionHistoryState.collectAsStateWithLifecycle().run {
|
||||
transactionHistoryViewModel.processTransactionState(value)
|
||||
}
|
||||
|
||||
transactionHistoryViewModel.processTransactionState(transactionHistoryState)
|
||||
WrapAccount(
|
||||
context = activity.applicationContext,
|
||||
goBalances = goBalances,
|
||||
goSettings = goSettings,
|
||||
isKeepScreenOnWhileSyncing = isKeepScreenOnWhileSyncing,
|
||||
scope = scope,
|
||||
synchronizer = synchronizer,
|
||||
transactionHistoryViewModel = transactionHistoryViewModel,
|
||||
transactionsUiState = transactionsUiState,
|
||||
walletSnapshot = walletSnapshot,
|
||||
)
|
||||
|
||||
if (null == walletSnapshot) {
|
||||
// For benchmarking purposes
|
||||
activity.reportFullyDrawn()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@VisibleForTesting
|
||||
@Suppress("LongParameterList")
|
||||
internal fun WrapAccount(
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
goBalances: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
transactionsUiState: TransactionUiState,
|
||||
synchronizer: Synchronizer?,
|
||||
transactionHistoryViewModel: TransactionHistoryViewModel,
|
||||
walletSnapshot: WalletSnapshot?,
|
||||
isKeepScreenOnWhileSyncing: Boolean?,
|
||||
) {
|
||||
if (null == synchronizer || null == walletSnapshot) {
|
||||
// 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
|
||||
|
@ -59,39 +92,44 @@ internal fun WrapAccount(
|
|||
transactionsUiState = transactionsUiState,
|
||||
onTransactionItemAction = { action ->
|
||||
when (action) {
|
||||
is TransactionItemAction.IdClick -> {
|
||||
Twig.info { "Transaction ID clicked: ${action.id}" }
|
||||
is TrxItemAction.TransactionIdClick -> {
|
||||
Twig.info { "Transaction ID clicked" }
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.account_history_id_clipboard_tag),
|
||||
context,
|
||||
context.getString(R.string.account_history_id_clipboard_tag),
|
||||
action.id
|
||||
)
|
||||
}
|
||||
is TransactionItemAction.MemoClick -> {
|
||||
Twig.info { "Transaction item clicked - querying memos..." }
|
||||
val memos = synchronizer?.getMemos(action.overview)
|
||||
is TrxItemAction.ExpandableStateChange -> {
|
||||
Twig.info { "Transaction new state: ${action.newState.name}" }
|
||||
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.account_history_item_clipboard_tag),
|
||||
merged
|
||||
)
|
||||
}
|
||||
transactionHistoryViewModel.updateTransactionItemState(
|
||||
synchronizer = synchronizer,
|
||||
txId = action.txId,
|
||||
newState = action.newState
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionItemAction.ExpandableStateChange -> {
|
||||
transactionHistoryViewModel.updateTransactionItemState(action.txId, action.newState)
|
||||
is TrxItemAction.AddressClick -> {
|
||||
Twig.info { "Transaction address clicked" }
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
context.applicationContext,
|
||||
context.getString(R.string.account_history_address_clipboard_tag),
|
||||
action.address.addressValue
|
||||
)
|
||||
}
|
||||
is TrxItemAction.MessageClick -> {
|
||||
Twig.info { "Transaction message clicked" }
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
context.applicationContext,
|
||||
context.getString(R.string.account_history_memo_clipboard_tag),
|
||||
action.memo
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
goBalances = goBalances,
|
||||
goSettings = goSettings,
|
||||
)
|
||||
|
||||
// For benchmarking purposes
|
||||
activity.reportFullyDrawn()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package co.electriccoin.zcash.ui.screen.account.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
|
||||
import cash.z.ecc.android.sdk.fixture.WalletFixture
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
||||
|
||||
object TransactionUiFixture {
|
||||
val OVERVIEW: TransactionOverview = TransactionOverviewFixture.new()
|
||||
|
||||
val RECIPIENT: TransactionRecipient =
|
||||
TransactionRecipient.Address(
|
||||
WalletFixture.Alice.getAddresses(ZcashNetwork.Mainnet).sapling
|
||||
)
|
||||
|
||||
val EXPANDABLE_STATE: TrxItemState = TrxItemState.COLLAPSED
|
||||
|
||||
val MESSAGES: List<String> = listOf("Thanks for the coffee", "It was great to meet you!")
|
||||
|
||||
internal fun new(
|
||||
overview: TransactionOverview = OVERVIEW,
|
||||
recipient: TransactionRecipient = RECIPIENT,
|
||||
expandableState: TrxItemState = EXPANDABLE_STATE,
|
||||
messages: List<String> = MESSAGES,
|
||||
) = TransactionUi(
|
||||
overview = overview,
|
||||
recipient = recipient,
|
||||
expandableState = expandableState,
|
||||
messages = messages
|
||||
)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package co.electriccoin.zcash.ui.screen.account.fixture
|
||||
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
object TransactionsFixture {
|
||||
val TRANSACTIONS: ImmutableList<TransactionUi> =
|
||||
persistentListOf(
|
||||
TransactionUiFixture.new(),
|
||||
TransactionUiFixture.new(),
|
||||
TransactionUiFixture.new(),
|
||||
TransactionUiFixture.new()
|
||||
)
|
||||
|
||||
internal fun new() = TRANSACTIONS
|
||||
}
|
|
@ -7,16 +7,19 @@ import co.electriccoin.zcash.ui.screen.account.ext.TransactionOverviewExt
|
|||
data class TransactionUi(
|
||||
val overview: TransactionOverview,
|
||||
val recipient: TransactionRecipient?,
|
||||
val expandableState: HistoryItemExpandableState
|
||||
val expandableState: TrxItemState,
|
||||
val messages: List<String>?
|
||||
) {
|
||||
companion object {
|
||||
fun new(
|
||||
data: TransactionOverviewExt,
|
||||
expandableState: HistoryItemExpandableState
|
||||
expandableState: TrxItemState,
|
||||
messages: List<String>?
|
||||
) = TransactionUi(
|
||||
overview = data.overview,
|
||||
recipient = data.recipient,
|
||||
expandableState = expandableState
|
||||
expandableState = expandableState,
|
||||
messages = messages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package co.electriccoin.zcash.ui.screen.account.model
|
||||
|
||||
enum class HistoryItemExpandableState {
|
||||
enum class TrxItemState {
|
||||
COLLAPSED,
|
||||
EXPANDED,
|
||||
EXPANDED_ADDRESS,
|
||||
EXPANDED_ID
|
||||
EXPANDED_ID,
|
||||
EXPANDED_ALL,
|
||||
}
|
|
@ -16,8 +16,6 @@ import androidx.compose.ui.res.painterResource
|
|||
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.Zatoshi
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidget
|
||||
import co.electriccoin.zcash.ui.common.compose.DisableScreenTimeout
|
||||
|
@ -28,10 +26,8 @@ import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
|||
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.model.HistoryItemExpandableState
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Preview("Account No History")
|
||||
@Composable
|
||||
|
@ -61,27 +57,7 @@ private fun HistoryListComposablePreview() {
|
|||
isKeepScreenOnWhileSyncing = false,
|
||||
goBalances = {},
|
||||
goSettings = {},
|
||||
transactionsUiState =
|
||||
TransactionUiState.Prepared(
|
||||
transactions =
|
||||
persistentListOf(
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.EXPANDED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
)
|
||||
),
|
||||
transactionsUiState = TransactionUiState.Prepared(transactions = TransactionsFixture.new()),
|
||||
onTransactionItemAction = {},
|
||||
)
|
||||
}
|
||||
|
@ -94,7 +70,7 @@ internal fun Account(
|
|||
goBalances: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
isKeepScreenOnWhileSyncing: Boolean?,
|
||||
onTransactionItemAction: (TransactionItemAction) -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
transactionsUiState: TransactionUiState,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
) {
|
||||
|
@ -141,7 +117,7 @@ private fun AccountMainContent(
|
|||
walletSnapshot: WalletSnapshot,
|
||||
isKeepScreenOnWhileSyncing: Boolean?,
|
||||
goBalances: () -> Unit,
|
||||
onTransactionItemAction: (TransactionItemAction) -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
transactionState: TransactionUiState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
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.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DividerDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
|
@ -22,6 +27,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.testTag
|
||||
|
@ -31,25 +37,26 @@ 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 androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||
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 co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.CircularMidProgressIndicator
|
||||
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.component.TextWithIcon
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.account.HistoryTag
|
||||
import co.electriccoin.zcash.ui.screen.account.model.HistoryItemExpandableState
|
||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionUiFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
@ -72,41 +79,19 @@ private fun ComposablePreview() {
|
|||
private fun ComposableHistoryListPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
@Suppress("MagicNumber")
|
||||
HistoryContainer(
|
||||
transactionState =
|
||||
TransactionUiState.Prepared(
|
||||
transactions =
|
||||
persistentListOf(
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.EXPANDED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(200000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(300000000)),
|
||||
null,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
),
|
||||
)
|
||||
),
|
||||
transactionState = TransactionUiState.Prepared(transactions = TransactionsFixture.new()),
|
||||
onTransactionItemAction = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dateFormat: DateFormat by lazy {
|
||||
SimpleDateFormat.getDateTimeInstance(
|
||||
SimpleDateFormat.MEDIUM,
|
||||
SimpleDateFormat.SHORT,
|
||||
// TODO [#1171]: Remove default MonetarySeparators locale
|
||||
// TODO [#1171]: https://github.com/Electric-Coin-Company/zashi-android/issues/1171
|
||||
// TODO [#1171]: Remove default MonetarySeparators locale
|
||||
// TODO [#1171]: https://github.com/Electric-Coin-Company/zashi-android/issues/1171
|
||||
private val dateFormat: DateFormat by lazy {
|
||||
SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
Locale.US
|
||||
)
|
||||
}
|
||||
|
@ -114,7 +99,7 @@ val dateFormat: DateFormat by lazy {
|
|||
@Composable
|
||||
internal fun HistoryContainer(
|
||||
transactionState: TransactionUiState,
|
||||
onTransactionItemAction: (TransactionItemAction) -> Unit,
|
||||
onTransactionItemAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
|
@ -166,10 +151,11 @@ internal fun HistoryContainer(
|
|||
@Composable
|
||||
private fun HistoryList(
|
||||
transactions: ImmutableList<TransactionUi>,
|
||||
onAction: (TransactionItemAction) -> Unit
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.testTag(HistoryTag.TRANSACTION_LIST)
|
||||
modifier = modifier.then(Modifier.testTag(HistoryTag.TRANSACTION_LIST))
|
||||
) {
|
||||
items(transactions.size) { index ->
|
||||
HistoryItem(
|
||||
|
@ -191,15 +177,9 @@ private fun HistoryList(
|
|||
private fun ComposableHistoryListItemPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
@Suppress("MagicNumber")
|
||||
HistoryItem(
|
||||
onAction = {},
|
||||
transaction =
|
||||
TransactionUi(
|
||||
TransactionOverviewFixture.new(netValue = Zatoshi(100000000)),
|
||||
recipient = null,
|
||||
expandableState = HistoryItemExpandableState.EXPANDED
|
||||
)
|
||||
transaction = TransactionUiFixture.new()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -208,10 +188,10 @@ private fun ComposableHistoryListItemPreview() {
|
|||
const val ADDRESS_IN_TITLE_WIDTH_RATIO = 0.5f
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Suppress("LongMethod")
|
||||
private fun HistoryItem(
|
||||
transaction: TransactionUi,
|
||||
onAction: (TransactionItemAction) -> Unit,
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val typeText: String
|
||||
|
@ -221,38 +201,38 @@ private fun HistoryItem(
|
|||
when (transaction.overview.getExtendedState()) {
|
||||
TransactionExtendedState.SENT -> {
|
||||
typeText = stringResource(id = R.string.account_history_item_sent)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.trx_send_icon)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_send_icon)
|
||||
textColor = MaterialTheme.colorScheme.onBackground
|
||||
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRegular
|
||||
}
|
||||
TransactionExtendedState.SENDING -> {
|
||||
typeText = stringResource(id = R.string.account_history_item_sending)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.trx_send_icon)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_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)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_send_icon)
|
||||
textColor = ZcashTheme.colors.dangerous
|
||||
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleFailed
|
||||
}
|
||||
|
||||
TransactionExtendedState.RECEIVED -> {
|
||||
typeText = stringResource(id = R.string.account_history_item_received)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_receive_icon)
|
||||
textColor = MaterialTheme.colorScheme.onBackground
|
||||
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRegular
|
||||
}
|
||||
TransactionExtendedState.RECEIVING -> {
|
||||
typeText = stringResource(id = R.string.account_history_item_receiving)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_receive_icon)
|
||||
textColor = ZcashTheme.colors.textDescription
|
||||
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleRunning
|
||||
}
|
||||
TransactionExtendedState.RECEIVE_FAILED -> {
|
||||
typeText = stringResource(id = R.string.account_history_item_receive_failed)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.trx_receive_icon)
|
||||
typeIcon = ImageVector.vectorResource(R.drawable.ic_trx_receive_icon)
|
||||
textColor = ZcashTheme.colors.dangerous
|
||||
textStyle = ZcashTheme.extendedTypography.transactionItemStyles.titleFailed
|
||||
}
|
||||
|
@ -260,161 +240,437 @@ private fun HistoryItem(
|
|||
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.then(
|
||||
Modifier
|
||||
.background(color = ZcashTheme.colors.historyBackgroundColor)
|
||||
.clickable {
|
||||
if (transaction.expandableState <= HistoryItemExpandableState.COLLAPSED) {
|
||||
onAction(
|
||||
TransactionItemAction.ExpandableStateChange(
|
||||
transaction.overview.rawId,
|
||||
HistoryItemExpandableState.EXPANDED
|
||||
)
|
||||
modifier.then(
|
||||
Modifier
|
||||
.background(color = ZcashTheme.colors.historyBackgroundColor)
|
||||
.clickable {
|
||||
if (transaction.expandableState <= TrxItemState.COLLAPSED) {
|
||||
onAction(
|
||||
TrxItemAction.ExpandableStateChange(
|
||||
transaction.overview.rawId,
|
||||
TrxItemState.EXPANDED
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.padding(all = ZcashTheme.dimens.spacingLarge)
|
||||
.animateContentSize()
|
||||
)
|
||||
}
|
||||
.padding(all = ZcashTheme.dimens.spacingLarge)
|
||||
.animateContentSize()
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
imageVector = typeIcon,
|
||||
contentDescription = typeText,
|
||||
contentDescription = typeText
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = typeText,
|
||||
style = textStyle,
|
||||
color = textColor,
|
||||
modifier = Modifier.testTag(HistoryTag.TRANSACTION_ITEM_TITLE)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
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 dateString =
|
||||
transaction.overview.minedHeight?.let {
|
||||
transaction.overview.blockTimeEpochSeconds?.let { blockTimeEpochSeconds ->
|
||||
// * 1000 to covert to millis
|
||||
@Suppress("MagicNumber")
|
||||
dateFormat.format(blockTimeEpochSeconds.times(1000))
|
||||
} ?: ""
|
||||
} ?: ""
|
||||
|
||||
Text(
|
||||
text = dateString,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.date,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
HistoryItemCollapsedMainPart(
|
||||
transaction = transaction,
|
||||
typeText = typeText,
|
||||
textStyle = textStyle,
|
||||
textColor = textColor,
|
||||
onAction = onAction
|
||||
)
|
||||
|
||||
if (transaction.expandableState == HistoryItemExpandableState.EXPANDED) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingXtiny))
|
||||
|
||||
val txId = transaction.overview.txIdString()
|
||||
Tiny(
|
||||
text = txId,
|
||||
modifier =
|
||||
Modifier
|
||||
.clickable { onAction(TransactionItemAction.IdClick(txId)) }
|
||||
.testTag(HistoryTag.TRANSACTION_ID)
|
||||
)
|
||||
// To add an extra spacing at the end
|
||||
Column(
|
||||
modifier = Modifier.padding(end = ZcashTheme.dimens.spacingUpLarge)
|
||||
) {
|
||||
val isInExpectedState =
|
||||
transaction.expandableState == TrxItemState.EXPANDED_ADDRESS ||
|
||||
transaction.expandableState == TrxItemState.EXPANDED_ALL
|
||||
|
||||
Spacer(modifier = (Modifier.height(ZcashTheme.dimens.spacingDefault)))
|
||||
if (isInExpectedState &&
|
||||
transaction.recipient != null &&
|
||||
transaction.recipient is TransactionRecipient.Address
|
||||
) {
|
||||
HistoryItemExpandedAddressPart(onAction, transaction.recipient)
|
||||
|
||||
// TODO [#1162]: Will be reworked
|
||||
// TODO [#1162]: Expandable transaction history item
|
||||
// TODO [#1162]: https://github.com/Electric-Coin-Company/zashi-android/issues/1162
|
||||
Tiny(
|
||||
text = "Tap to copy message",
|
||||
modifier = Modifier.clickable { onAction(TransactionItemAction.MemoClick(transaction.overview)) }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
}
|
||||
|
||||
Spacer(modifier = (Modifier.height(ZcashTheme.dimens.spacingDefault)))
|
||||
HistoryItemDatePart(transaction)
|
||||
|
||||
Tiny(
|
||||
text = stringResource(id = R.string.account_history_item_collapse_transaction),
|
||||
modifier =
|
||||
Modifier
|
||||
.clickable {
|
||||
if (transaction.expandableState >= HistoryItemExpandableState.EXPANDED) {
|
||||
if (transaction.expandableState >= TrxItemState.EXPANDED) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
HistoryItemExpandedPart(onAction, transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
private fun HistoryItemCollapsedMainPart(
|
||||
transaction: TransactionUi,
|
||||
typeText: String,
|
||||
textStyle: TextStyle,
|
||||
textColor: Color,
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
modifier.then(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 24.dp)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = typeText,
|
||||
style = textStyle,
|
||||
color = textColor,
|
||||
modifier = Modifier.testTag(HistoryTag.TRANSACTION_ITEM_TITLE)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
HistoryItemCollapsedAddressPart(onAction, transaction)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryItemCollapsedAddressPart(
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
transaction: TransactionUi,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (transaction.recipient != null && transaction.recipient is TransactionRecipient.Address) {
|
||||
when (transaction.expandableState) {
|
||||
TrxItemState.EXPANDED_ADDRESS, TrxItemState.EXPANDED_ALL -> {
|
||||
// No address displayed in the top row
|
||||
}
|
||||
else -> {
|
||||
val clickModifier =
|
||||
modifier.then(
|
||||
if (transaction.expandableState <= TrxItemState.COLLAPSED) {
|
||||
Modifier.padding(all = ZcashTheme.dimens.spacingXtiny)
|
||||
} else {
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable {
|
||||
onAction(
|
||||
TransactionItemAction.ExpandableStateChange(
|
||||
TrxItemAction.ExpandableStateChange(
|
||||
transaction.overview.rawId,
|
||||
HistoryItemExpandableState.COLLAPSED
|
||||
if (transaction.expandableState == TrxItemState.EXPANDED_ID) {
|
||||
TrxItemState.EXPANDED_ALL
|
||||
} else {
|
||||
TrxItemState.EXPANDED_ADDRESS
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(all = ZcashTheme.dimens.spacingXtiny)
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
.then(clickModifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_trx_shielded),
|
||||
contentDescription = stringResource(id = R.string.account_history_item_shielded)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const val EXPANDED_ADDRESS_WIDTH_RATIO = 0.75f
|
||||
|
||||
@Composable
|
||||
private fun HistoryItemExpandedAddressPart(
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
recipient: TransactionRecipient.Address,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
modifier.then(
|
||||
Modifier.fillMaxWidth()
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = recipient.addressValue,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textCommon,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(EXPANDED_ADDRESS_WIDTH_RATIO)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
TextWithIcon(
|
||||
text = stringResource(id = R.string.account_history_item_tap_to_copy),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_trx_copy),
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable { onAction(TrxItemAction.AddressClick(recipient)) }
|
||||
.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryItemDatePart(
|
||||
transaction: TransactionUi,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val formattedDate =
|
||||
transaction.overview.blockTimeEpochSeconds?.let { blockTimeEpochSeconds ->
|
||||
// * 1000 to covert to millis
|
||||
@Suppress("MagicNumber")
|
||||
dateFormat.format(blockTimeEpochSeconds.times(1000))
|
||||
}
|
||||
|
||||
if (formattedDate != null) {
|
||||
Text(
|
||||
text = formattedDate,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryItemExpandedPart(
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
transaction: TransactionUi,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!transaction.messages.isNullOrEmpty()) {
|
||||
HistoryItemMessagePart(transaction.messages.toPersistentList(), onAction)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
}
|
||||
|
||||
HistoryItemTransactionIdPart(
|
||||
transaction = transaction,
|
||||
onAction = onAction
|
||||
)
|
||||
|
||||
Spacer(modifier = (Modifier.height(ZcashTheme.dimens.spacingDefault)))
|
||||
|
||||
TextWithIcon(
|
||||
text = stringResource(id = R.string.account_history_item_collapse_transaction),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.contentUnderline,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_trx_collapse),
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable {
|
||||
if (transaction.expandableState >= TrxItemState.EXPANDED) {
|
||||
onAction(
|
||||
TrxItemAction.ExpandableStateChange(
|
||||
transaction.overview.rawId,
|
||||
TrxItemState.COLLAPSED
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const val EXPANDED_TRANSACTION_ID_WIDTH_RATIO = 0.75f
|
||||
const val COLLAPSED_TRANSACTION_ID_WIDTH_RATIO = 0.5f
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
private fun HistoryItemTransactionIdPart(
|
||||
transaction: TransactionUi,
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val txIdString = transaction.overview.txIdString()
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (transaction.expandableState == TrxItemState.EXPANDED_ID ||
|
||||
transaction.expandableState == TrxItemState.EXPANDED_ALL
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.account_history_item_transaction_id),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingXtiny))
|
||||
|
||||
Text(
|
||||
text = txIdString,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textCommon,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(EXPANDED_TRANSACTION_ID_WIDTH_RATIO)
|
||||
.testTag(HistoryTag.TRANSACTION_ID)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
TextWithIcon(
|
||||
text = stringResource(id = R.string.account_history_item_tap_to_copy),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_trx_copy),
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable { onAction(TrxItemAction.TransactionIdClick(txIdString)) }
|
||||
.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
)
|
||||
} else {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable {
|
||||
onAction(
|
||||
TrxItemAction.ExpandableStateChange(
|
||||
transaction.overview.rawId,
|
||||
if (transaction.expandableState == TrxItemState.EXPANDED_ADDRESS) {
|
||||
TrxItemState.EXPANDED_ALL
|
||||
} else {
|
||||
TrxItemState.EXPANDED_ID
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.account_history_item_transaction_id),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Text(
|
||||
text = txIdString,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(COLLAPSED_TRANSACTION_ID_WIDTH_RATIO)
|
||||
.testTag(HistoryTag.TRANSACTION_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TransactionItemAction {
|
||||
data class IdClick(val id: String) : TransactionItemAction()
|
||||
@Composable
|
||||
private fun HistoryItemMessagePart(
|
||||
messages: ImmutableList<String>,
|
||||
onAction: (TrxItemAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val composedMessage = messages.joinToString(separator = "\n\n")
|
||||
|
||||
Column(modifier = modifier.then(Modifier.fillMaxWidth())) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.account_history_item_message),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.contentMedium,
|
||||
color = ZcashTheme.colors.textMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.border(width = 1.dp, color = ZcashTheme.colors.textFieldFrame)
|
||||
) {
|
||||
Text(
|
||||
text = composedMessage,
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textCommon,
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingMid)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
TextWithIcon(
|
||||
text = stringResource(id = R.string.account_history_item_tap_to_copy),
|
||||
style = ZcashTheme.extendedTypography.transactionItemStyles.content,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_trx_copy),
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.regularRippleEffectCorner))
|
||||
.clickable { onAction(TrxItemAction.MessageClick(composedMessage)) }
|
||||
.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TrxItemAction {
|
||||
data class TransactionIdClick(val id: String) : TrxItemAction()
|
||||
|
||||
data class ExpandableStateChange(
|
||||
val txId: FirstClassByteArray,
|
||||
val newState: HistoryItemExpandableState
|
||||
) : TransactionItemAction()
|
||||
val newState: TrxItemState
|
||||
) : TrxItemAction()
|
||||
|
||||
data class MemoClick(val overview: TransactionOverview) : TransactionItemAction()
|
||||
data class AddressClick(val address: TransactionRecipient.Address) : TrxItemAction()
|
||||
|
||||
data class MessageClick(val memo: String) : TrxItemAction()
|
||||
}
|
||||
|
||||
internal enum class TransactionExtendedState {
|
||||
|
|
|
@ -3,11 +3,14 @@ package co.electriccoin.zcash.ui.screen.account.viewmodel
|
|||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.ui.screen.account.model.HistoryItemExpandableState
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUi
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TrxItemState
|
||||
import co.electriccoin.zcash.ui.screen.account.state.TransactionHistorySyncState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
@ -18,6 +21,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.toList
|
||||
|
||||
class TransactionHistoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val transactions: MutableStateFlow<ImmutableList<TransactionUi>> = MutableStateFlow(persistentListOf())
|
||||
|
@ -41,29 +45,59 @@ class TransactionHistoryViewModel(application: Application) : AndroidViewModel(a
|
|||
TransactionHistorySyncState.Loading -> persistentListOf()
|
||||
is TransactionHistorySyncState.Prepared -> {
|
||||
dataState.transactions.map { data ->
|
||||
val existingTransaction =
|
||||
transactions.value.find {
|
||||
data.overview.rawId == it.overview.rawId
|
||||
}
|
||||
TransactionUi.new(
|
||||
data = data,
|
||||
expandableState =
|
||||
transactions.value.find {
|
||||
data.overview.rawId == it.overview.rawId
|
||||
}?.expandableState ?: HistoryItemExpandableState.COLLAPSED
|
||||
expandableState = existingTransaction?.expandableState ?: TrxItemState.COLLAPSED,
|
||||
messages = existingTransaction?.messages,
|
||||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTransactionItemState(
|
||||
txId: FirstClassByteArray,
|
||||
newState: HistoryItemExpandableState
|
||||
) {
|
||||
private fun updateTransaction(newTransaction: TransactionUi) {
|
||||
transactions.value =
|
||||
transactions.value.map { item ->
|
||||
if (item.overview.rawId == txId) {
|
||||
item.copy(expandableState = newState)
|
||||
if (item.overview.rawId == newTransaction.overview.rawId) {
|
||||
newTransaction
|
||||
} else {
|
||||
item
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
suspend fun updateTransactionItemState(
|
||||
synchronizer: Synchronizer,
|
||||
txId: FirstClassByteArray,
|
||||
newState: TrxItemState
|
||||
) {
|
||||
val updated =
|
||||
transactions.value
|
||||
.find { it.overview.rawId == txId }
|
||||
?.copy(expandableState = newState)
|
||||
|
||||
if (updated != null) {
|
||||
var updatedWithMessages = updated
|
||||
// Expanding the item on the first time -> load messages
|
||||
if (newState == TrxItemState.EXPANDED && updated.messages == null) {
|
||||
val messages = loadMessageForTransaction(synchronizer, updated.overview)
|
||||
updatedWithMessages = updated.copy(messages = messages)
|
||||
}
|
||||
updateTransaction(updatedWithMessages)
|
||||
} else {
|
||||
Twig.warn { "Transaction not found" }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadMessageForTransaction(
|
||||
synchronizer: Synchronizer,
|
||||
overview: TransactionOverview
|
||||
): List<String> =
|
||||
synchronizer.getMemos(overview).toList().also {
|
||||
Twig.info { "Transaction messages count: ${it.size}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M1,1h10v10h-10z"
|
||||
android:strokeWidth="0.25"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#777777"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M7.667,6.833L6,5.167L4.333,6.833"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#F4B728"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="11dp"
|
||||
android:height="10dp"
|
||||
android:viewportWidth="11"
|
||||
android:viewportHeight="10">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h10.25v9.75h-10.25z"/>
|
||||
<path
|
||||
android:pathData="M0,9.75V1.72H8.46V9.75H0ZM7.58,8.9V2.57H0.88V8.91H7.58V8.9Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M9.81,7.2C9.57,7.2 9.37,7.01 9.37,6.78V0.85H3.1C2.86,0.85 2.66,0.66 2.66,0.43C2.66,0.2 2.86,0.01 3.1,0.01H10.25V6.79C10.25,7.02 10.05,7.21 9.81,7.21V7.2Z"
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -11,8 +11,12 @@
|
|||
<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_tap_to_copy">Tap to copy</string>
|
||||
<string name="account_history_item_message">Message</string>
|
||||
<string name="account_history_item_collapse_transaction">Collapse transaction</string>
|
||||
<string name="account_history_item_transaction_id">Transaction ID</string>
|
||||
|
||||
<string name="account_history_item_clipboard_tag">Transaction memo</string>
|
||||
<string name="account_history_memo_clipboard_tag">Transaction message</string>
|
||||
<string name="account_history_id_clipboard_tag">Transaction ID</string>
|
||||
<string name="account_history_address_clipboard_tag">Transaction address</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue