demo: minor UI improvements

This commit is contained in:
Oscar Mira 2023-10-22 21:39:43 +02:00
parent 21e8c77e26
commit 894788f3b2
11 changed files with 99 additions and 40 deletions

View File

@ -1,6 +1,7 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -9,6 +10,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.Transaction
import im.molly.monero.demo.ui.preview.PreviewParameterData import im.molly.monero.demo.ui.preview.PreviewParameterData
import im.molly.monero.demo.ui.theme.AppTheme 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -25,29 +31,47 @@ fun TransactionCardExpanded(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
Card( Card(
onClick = onClick, 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( Column(
modifier = Modifier.padding(12.dp), modifier = Modifier.padding(14.dp)
) { ) {
TransactionDetail("State", transaction.state.toString()) Row(
Spacer(modifier = Modifier.height(12.dp)) horizontalArrangement = Arrangement.SpaceBetween,
TransactionDetail("Sent", transaction.sent.toString()) modifier = modifier.fillMaxWidth(),
Spacer(modifier = Modifier.height(12.dp)) ) {
TransactionDetail("Received", transaction.received.toString()) val timestamp = transaction.timestamp?.let {
Spacer(modifier = Modifier.height(12.dp)) val localDateTime = it.atZone(ZoneId.systemDefault()).toLocalDateTime()
TransactionDetail("Payments", transaction.payments.toString()) localDateTime.format(formatter)
Spacer(modifier = Modifier.height(12.dp)) }
TransactionDetail("Time lock", transaction.timeLock.toString()) Text(
Spacer(modifier = Modifier.height(12.dp)) text = timestamp ?: "",
TransactionDetail("Change", MoneroCurrency.format(transaction.change)) style = MaterialTheme.typography.titleLarge,
Spacer(modifier = Modifier.height(12.dp)) )
TransactionDetail("Fee", MoneroCurrency.format(transaction.fee)) Text(
Spacer(modifier = Modifier.height(12.dp)) text = MoneroCurrency.format(transaction.amount, precision = 5),
TransactionDetail("Transaction ID", transaction.txId) color = if (transaction.amount < 0) Red40 else Blue40,
Spacer(modifier = Modifier.height(12.dp)) 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<List<Transaction>> { private class TransactionCardPreviewParameterProvider :
PreviewParameterProvider<List<Transaction>> {
override val values = sequenceOf(PreviewParameterData.transactions) override val values = sequenceOf(PreviewParameterData.transactions)
} }

View File

@ -80,6 +80,8 @@ private fun TransactionScreen(
val tx = uiState.transaction val tx = uiState.transaction
TransactionDetail("State", tx.state.toString()) TransactionDetail("State", tx.state.toString())
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
TransactionDetail("Amount", MoneroCurrency.format(tx.amount))
Spacer(modifier = Modifier.height(12.dp))
TransactionDetail("Sent", tx.sent.toString()) TransactionDetail("Sent", tx.sent.toString())
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
TransactionDetail("Received", tx.received.toString()) TransactionDetail("Received", tx.received.toString())

View File

@ -2,6 +2,7 @@ package im.molly.monero.demo.ui
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -33,17 +34,16 @@ fun WalletCard(
val uiState: WalletUiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState: WalletUiState by viewModel.uiState.collectAsStateWithLifecycle()
Card( 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) { when (uiState) {
is WalletUiState.Loaded -> { is WalletUiState.Loaded -> WalletCardExpanded(uiState as WalletUiState.Loaded)
WalletCardExpanded(
(uiState as WalletUiState.Loaded).config,
(uiState as WalletUiState.Loaded).balance,
)
}
WalletUiState.Error -> WalletCardError() WalletUiState.Error -> WalletCardError()
WalletUiState.Loading -> WalletCardLoading() WalletUiState.Loading -> WalletCardLoading()
} }
@ -53,14 +53,13 @@ fun WalletCard(
@Composable @Composable
fun WalletCardExpanded( fun WalletCardExpanded(
config: WalletConfig, uiState: WalletUiState.Loaded,
balance: Balance,
) { ) {
Row { Row {
Text(text = "Name: ${config.name}") Text(text = "Wallet ID #${uiState.config.id} (${uiState.config.name}) ${uiState.config.publicAddress}")
} }
Row { Row {
Text(text = "Ledger: $balance") Text(text = uiState.blockchainTime.toString())
} }
} }

View File

@ -100,7 +100,7 @@ private fun WalletScreenLoaded(
.padding(padding), .padding(padding),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text(style = MaterialTheme.typography.headlineLarge, text = buildAnnotatedString { Text(style = MaterialTheme.typography.headlineMedium, text = buildAnnotatedString {
append(MoneroCurrency.SYMBOL + " ") append(MoneroCurrency.SYMBOL + " ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append( append(

View File

@ -62,7 +62,9 @@ private fun walletUiState(
val balance = ledger.balance val balance = ledger.balance
val blockchainTime = ledger.checkedAt val blockchainTime = ledger.checkedAt
val transactions = 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) WalletUiState.Loaded(config, blockchainTime, balance, transactions)
} }

View File

@ -8,4 +8,6 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4) val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71) val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260) val Pink40 = Color(0xFF7D5260)
val Red40 = Color(0xFFBA1B1B)
val Blue40 = Color(0xFF1546F6)

View File

@ -23,4 +23,16 @@ class Enote(
override fun hashCode() = System.identityHashCode(this) override fun hashCode() = System.identityHashCode(this)
override fun equals(other: Any?) = this === other 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" +
")"
}
} }

View File

@ -2,6 +2,7 @@ package im.molly.monero
import java.text.NumberFormat import java.text.NumberFormat
import java.util.Locale import java.util.Locale
import kotlin.math.absoluteValue
object MoneroCurrency { object MoneroCurrency {
const val SYMBOL = "XMR" const val SYMBOL = "XMR"
@ -33,7 +34,11 @@ object MoneroCurrency {
val ExactFormat = object : Format(MoneroAmount.ATOMIC_UNIT_SCALE) { val ExactFormat = object : Format(MoneroAmount.ATOMIC_UNIT_SCALE) {
override fun format(amount: MoneroAmount) = buildString { 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) { if (precision < num.length) {
val point = num.length - precision val point = num.length - precision
@ -50,8 +55,11 @@ object MoneroCurrency {
} }
} }
fun format(amount: MoneroAmount, outputFormat: Format = ExactFormat): String { fun format(amount: MoneroAmount, outputFormat: Format = ExactFormat): String {
return outputFormat.format(amount) return outputFormat.format(amount)
} }
fun format(amount: MoneroAmount, precision: Int): String {
return Format(precision = precision).format(amount)
}
} }

View File

@ -44,7 +44,7 @@ class MoneroWallet internal constructor(
private fun sendLedger(ledger: Ledger) { private fun sendLedger(ledger: Ledger) {
trySend(ledger).onFailure { trySend(ledger).onFailure {
logger.e("Too many ledger updates, channel capacity exceeded") logger.e("Too many ledger updates, channel capacity exceeded", it)
} }
} }
} }

View File

@ -5,4 +5,6 @@ value class PublicKey(private val publicKey: String) {
init { init {
require(publicKey.length == 64) { "Public key length must be 64 bytes" } require(publicKey.length == 64) { "Public key length must be 64 bytes" }
} }
override fun toString(): String = publicKey
} }

View File

@ -1,5 +1,7 @@
package im.molly.monero package im.molly.monero
import java.time.Instant
data class Transaction( data class Transaction(
val hash: HashDigest, val hash: HashDigest,
// TODO: val version: ProtocolInfo, // TODO: val version: ProtocolInfo,
@ -11,11 +13,16 @@ data class Transaction(
val fee: MoneroAmount, val fee: MoneroAmount,
val change: MoneroAmount, val change: MoneroAmount,
) { ) {
val amount: MoneroAmount = received.sumOf { it.amount } - sent.sumOf { it.amount }
val txId: String val txId: String
get() = hash.toString() get() = hash.toString()
val blockHeight: Int? val blockHeight: Int?
get() = (state as? TxState.OnChain)?.blockHeader?.height get() = (state as? TxState.OnChain)?.blockHeader?.height
val timestamp: Instant?
get() = (state as? TxState.OnChain)?.let { Instant.ofEpochSecond(it.blockHeader.epochSecond) }
} }
sealed interface TxState { sealed interface TxState {