mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-25 23:49:28 -05:00
demo: minor UI improvements
This commit is contained in:
parent
21e8c77e26
commit
894788f3b2
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,3 +9,5 @@ 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)
|
||||||
|
@ -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" +
|
||||||
|
")"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user