From 894788f3b2e95984c6d1f8f11b345c77cd1cc1c2 Mon Sep 17 00:00:00 2001 From: Oscar Mira Date: Sun, 22 Oct 2023 21:39:43 +0200 Subject: [PATCH] demo: minor UI improvements --- .../molly/monero/demo/ui/TransactionCard.kt | 67 +++++++++++++------ .../molly/monero/demo/ui/TransactionScreen.kt | 2 + .../im/molly/monero/demo/ui/WalletCard.kt | 25 ++++--- .../im/molly/monero/demo/ui/WalletScreen.kt | 2 +- .../molly/monero/demo/ui/WalletViewModel.kt | 4 +- .../im/molly/monero/demo/ui/theme/Color.kt | 4 +- .../src/main/kotlin/im/molly/monero/Enote.kt | 12 ++++ .../kotlin/im/molly/monero/MoneroCurrency.kt | 12 +++- .../kotlin/im/molly/monero/MoneroWallet.kt | 2 +- .../main/kotlin/im/molly/monero/PublicKey.kt | 2 + .../kotlin/im/molly/monero/Transaction.kt | 7 ++ 11 files changed, 99 insertions(+), 40 deletions(-) diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionCard.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionCard.kt index 0358e57..99fd908 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionCard.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionCard.kt @@ -1,6 +1,7 @@ +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api @@ -9,6 +10,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider @@ -17,6 +19,10 @@ import im.molly.monero.MoneroCurrency import im.molly.monero.Transaction import im.molly.monero.demo.ui.preview.PreviewParameterData import im.molly.monero.demo.ui.theme.AppTheme +import im.molly.monero.demo.ui.theme.Blue40 +import im.molly.monero.demo.ui.theme.Red40 +import java.time.ZoneId +import java.time.format.DateTimeFormatter @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -25,29 +31,47 @@ fun TransactionCardExpanded( onClick: () -> Unit, modifier: Modifier = Modifier, ) { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + Card( onClick = onClick, - modifier = modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp) + modifier = modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp), ) { Column( - modifier = Modifier.padding(12.dp), + modifier = Modifier.padding(14.dp) ) { - TransactionDetail("State", transaction.state.toString()) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Sent", transaction.sent.toString()) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Received", transaction.received.toString()) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Payments", transaction.payments.toString()) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Time lock", transaction.timeLock.toString()) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Change", MoneroCurrency.format(transaction.change)) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Fee", MoneroCurrency.format(transaction.fee)) - Spacer(modifier = Modifier.height(12.dp)) - TransactionDetail("Transaction ID", transaction.txId) - Spacer(modifier = Modifier.height(12.dp)) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier.fillMaxWidth(), + ) { + val timestamp = transaction.timestamp?.let { + val localDateTime = it.atZone(ZoneId.systemDefault()).toLocalDateTime() + localDateTime.format(formatter) + } + Text( + text = timestamp ?: "", + style = MaterialTheme.typography.titleLarge, + ) + Text( + text = MoneroCurrency.format(transaction.amount, precision = 5), + color = if (transaction.amount < 0) Red40 else Blue40, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + ) + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier.fillMaxWidth(), + ) { + Text( + text = "#${transaction.blockHeight}", + style = MaterialTheme.typography.titleSmall, + ) + Text( + text = "fee ${MoneroCurrency.format(transaction.fee, precision = 8)}", + style = MaterialTheme.typography.titleSmall, + ) + } } } } @@ -82,6 +106,7 @@ private fun TransactionCardExpandedPreview( } } -private class TransactionCardPreviewParameterProvider : PreviewParameterProvider> { +private class TransactionCardPreviewParameterProvider : + PreviewParameterProvider> { override val values = sequenceOf(PreviewParameterData.transactions) } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionScreen.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionScreen.kt index 35fdf18..6f03a72 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionScreen.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/TransactionScreen.kt @@ -80,6 +80,8 @@ private fun TransactionScreen( val tx = uiState.transaction TransactionDetail("State", tx.state.toString()) Spacer(modifier = Modifier.height(12.dp)) + TransactionDetail("Amount", MoneroCurrency.format(tx.amount)) + Spacer(modifier = Modifier.height(12.dp)) TransactionDetail("Sent", tx.sent.toString()) Spacer(modifier = Modifier.height(12.dp)) TransactionDetail("Received", tx.received.toString()) diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt index 3c4b051..9e9a1ec 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletCard.kt @@ -2,6 +2,7 @@ package im.molly.monero.demo.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api @@ -33,17 +34,16 @@ fun WalletCard( val uiState: WalletUiState by viewModel.uiState.collectAsStateWithLifecycle() Card( - onClick = onClick, modifier = modifier.padding(8.dp) + onClick = onClick, + modifier = modifier + .fillMaxWidth() + .padding(top = 8.dp, start = 8.dp, end = 8.dp), ) { - Column { + Column( + modifier = Modifier.padding(14.dp), + ) { when (uiState) { - is WalletUiState.Loaded -> { - WalletCardExpanded( - (uiState as WalletUiState.Loaded).config, - (uiState as WalletUiState.Loaded).balance, - ) - } - + is WalletUiState.Loaded -> WalletCardExpanded(uiState as WalletUiState.Loaded) WalletUiState.Error -> WalletCardError() WalletUiState.Loading -> WalletCardLoading() } @@ -53,14 +53,13 @@ fun WalletCard( @Composable fun WalletCardExpanded( - config: WalletConfig, - balance: Balance, + uiState: WalletUiState.Loaded, ) { Row { - Text(text = "Name: ${config.name}") + Text(text = "Wallet ID #${uiState.config.id} (${uiState.config.name}) ${uiState.config.publicAddress}") } Row { - Text(text = "Ledger: $balance") + Text(text = uiState.blockchainTime.toString()) } } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt index 1fdcec6..c294fa6 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletScreen.kt @@ -100,7 +100,7 @@ private fun WalletScreenLoaded( .padding(padding), horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(style = MaterialTheme.typography.headlineLarge, text = buildAnnotatedString { + Text(style = MaterialTheme.typography.headlineMedium, text = buildAnnotatedString { append(MoneroCurrency.SYMBOL + " ") withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { append( diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletViewModel.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletViewModel.kt index becc6d4..af4eb37 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletViewModel.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/WalletViewModel.kt @@ -62,7 +62,9 @@ private fun walletUiState( val balance = ledger.balance val blockchainTime = ledger.checkedAt val transactions = - ledger.transactions.map { WalletTransaction(config.id, it.value) } + ledger.transactions + .map { WalletTransaction(config.id, it.value) } + .sortedByDescending { it.transaction.timestamp } WalletUiState.Loaded(config, blockchainTime, balance, transactions) } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Color.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Color.kt index 2a7ab9e..d67d6fd 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Color.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/theme/Color.kt @@ -8,4 +8,6 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) +val Red40 = Color(0xFFBA1B1B) +val Blue40 = Color(0xFF1546F6) diff --git a/lib/android/src/main/kotlin/im/molly/monero/Enote.kt b/lib/android/src/main/kotlin/im/molly/monero/Enote.kt index de49778..97a47b9 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/Enote.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/Enote.kt @@ -23,4 +23,16 @@ class Enote( override fun hashCode() = System.identityHashCode(this) override fun equals(other: Any?) = this === other + + override fun toString(): String { + return "Enote(" + + "amount=${amount.xmr}" + + ", age=$age" + + ", spent=$spent" + + ", owner=$owner" + + ", key=$key" + + ", keyImage=$keyImage" + + ", sourceTxId=$sourceTxId" + + ")" + } } diff --git a/lib/android/src/main/kotlin/im/molly/monero/MoneroCurrency.kt b/lib/android/src/main/kotlin/im/molly/monero/MoneroCurrency.kt index 7615532..442bfac 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/MoneroCurrency.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/MoneroCurrency.kt @@ -2,6 +2,7 @@ package im.molly.monero import java.text.NumberFormat import java.util.Locale +import kotlin.math.absoluteValue object MoneroCurrency { const val SYMBOL = "XMR" @@ -33,7 +34,11 @@ object MoneroCurrency { val ExactFormat = object : Format(MoneroAmount.ATOMIC_UNIT_SCALE) { override fun format(amount: MoneroAmount) = buildString { - val num = amount.atomicUnits.toString() + if (amount.atomicUnits < 0) { + append('-') + } + + val num = amount.atomicUnits.absoluteValue.toString() if (precision < num.length) { val point = num.length - precision @@ -50,8 +55,11 @@ object MoneroCurrency { } } - fun format(amount: MoneroAmount, outputFormat: Format = ExactFormat): String { return outputFormat.format(amount) } + + fun format(amount: MoneroAmount, precision: Int): String { + return Format(precision = precision).format(amount) + } } diff --git a/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt b/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt index f2ef7c8..53545f5 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/MoneroWallet.kt @@ -44,7 +44,7 @@ class MoneroWallet internal constructor( private fun sendLedger(ledger: Ledger) { trySend(ledger).onFailure { - logger.e("Too many ledger updates, channel capacity exceeded") + logger.e("Too many ledger updates, channel capacity exceeded", it) } } } diff --git a/lib/android/src/main/kotlin/im/molly/monero/PublicKey.kt b/lib/android/src/main/kotlin/im/molly/monero/PublicKey.kt index 329dea3..fe42ac3 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/PublicKey.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/PublicKey.kt @@ -5,4 +5,6 @@ value class PublicKey(private val publicKey: String) { init { require(publicKey.length == 64) { "Public key length must be 64 bytes" } } + + override fun toString(): String = publicKey } diff --git a/lib/android/src/main/kotlin/im/molly/monero/Transaction.kt b/lib/android/src/main/kotlin/im/molly/monero/Transaction.kt index 022dba7..97d41ca 100644 --- a/lib/android/src/main/kotlin/im/molly/monero/Transaction.kt +++ b/lib/android/src/main/kotlin/im/molly/monero/Transaction.kt @@ -1,5 +1,7 @@ package im.molly.monero +import java.time.Instant + data class Transaction( val hash: HashDigest, // TODO: val version: ProtocolInfo, @@ -11,11 +13,16 @@ data class Transaction( val fee: MoneroAmount, val change: MoneroAmount, ) { + val amount: MoneroAmount = received.sumOf { it.amount } - sent.sumOf { it.amount } + val txId: String get() = hash.toString() val blockHeight: Int? get() = (state as? TxState.OnChain)?.blockHeader?.height + + val timestamp: Instant? + get() = (state as? TxState.OnChain)?.let { Instant.ofEpochSecond(it.blockHeader.epochSecond) } } sealed interface TxState {