mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2024-12-25 23:49:28 -05:00
demo: transaction screen and components
This commit is contained in:
parent
a84375d384
commit
42cf100951
@ -58,6 +58,9 @@ class WalletRepository(
|
|||||||
emitAll(getWallet(walletId).ledger())
|
emitAll(getWallet(walletId).ledger())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTransaction(walletId: Long, txId: String): Flow<Transaction?> =
|
||||||
|
getLedger(walletId).map { it.transactions[txId] }
|
||||||
|
|
||||||
suspend fun addWallet(
|
suspend fun addWallet(
|
||||||
moneroNetwork: MoneroNetwork,
|
moneroNetwork: MoneroNetwork,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package im.molly.monero.demo.data.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class Transaction(
|
|
||||||
@PrimaryKey val uid: Int,
|
|
||||||
)
|
|
@ -0,0 +1,8 @@
|
|||||||
|
package im.molly.monero.demo.data.model
|
||||||
|
|
||||||
|
import im.molly.monero.Transaction
|
||||||
|
|
||||||
|
data class WalletTransaction(
|
||||||
|
val walletId: Long,
|
||||||
|
val transaction: Transaction,
|
||||||
|
)
|
@ -1,20 +1,37 @@
|
|||||||
package im.molly.monero.demo.ui
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import im.molly.monero.demo.ui.component.Toolbar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryRoute(
|
fun HistoryRoute(
|
||||||
modifier: Modifier = Modifier,
|
navigateToTransaction: (String, Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
HistoryScreen(
|
HistoryScreen(
|
||||||
modifier = modifier,
|
onTransactionClick = navigateToTransaction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryScreen(
|
private fun HistoryScreen(
|
||||||
modifier: Modifier = Modifier,
|
onTransactionClick: (String, Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
Toolbar(
|
||||||
|
title = "Transaction history",
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ import im.molly.monero.demo.ui.component.Toolbar
|
|||||||
fun HomeRoute(
|
fun HomeRoute(
|
||||||
navigateToAddWalletWizard: () -> Unit,
|
navigateToAddWalletWizard: () -> Unit,
|
||||||
navigateToWallet: (Long) -> Unit,
|
navigateToWallet: (Long) -> Unit,
|
||||||
viewModel: HomeViewModel = viewModel(),
|
walletListViewModel: WalletListViewModel = viewModel(),
|
||||||
) {
|
) {
|
||||||
val walletListUiState: WalletListUiState by viewModel.walletListUiState.collectAsStateWithLifecycle()
|
val walletListUiState by walletListViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
walletListUiState = walletListUiState,
|
walletListUiState = walletListUiState,
|
||||||
@ -56,8 +56,8 @@ private fun HomeScreen(
|
|||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding))
|
.padding(padding),
|
||||||
{
|
) {
|
||||||
walletCards(walletListUiState, onWalletClick)
|
walletCards(walletListUiState, onWalletClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,8 +71,11 @@ private fun LazyListScope.walletCards(
|
|||||||
WalletListUiState.Loading -> item {
|
WalletListUiState.Loading -> item {
|
||||||
Text(text = "Loading wallet list...") // TODO
|
Text(text = "Loading wallet list...") // TODO
|
||||||
}
|
}
|
||||||
is WalletListUiState.Success -> {
|
|
||||||
walletCardsItems(walletListUiState.ids, onWalletClick)
|
is WalletListUiState.Loaded -> {
|
||||||
|
walletCardItems(walletListUiState.walletIds, onWalletClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is WalletListUiState.Empty -> Unit // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package im.molly.monero.demo.ui
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import im.molly.monero.demo.AppModule
|
|
||||||
import im.molly.monero.demo.data.WalletRepository
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
class HomeViewModel(
|
|
||||||
private val walletRepository: WalletRepository = AppModule.walletRepository,
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
val walletListUiState: StateFlow<WalletListUiState> = walletRepository.getWalletIdList()
|
|
||||||
.map { WalletListUiState.Success(it) }
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.WhileSubscribed(5_000),
|
|
||||||
initialValue = WalletListUiState.Loading,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface HomeUiState {
|
|
||||||
data object Loading : HomeUiState
|
|
||||||
|
|
||||||
// data class Ready(
|
|
||||||
// val wallets: List<WalletDetails>
|
|
||||||
// ) : HomeUiState
|
|
||||||
|
|
||||||
data object Empty : HomeUiState
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface WalletListUiState {
|
|
||||||
data class Success(val ids: List<Long>) : WalletListUiState
|
|
||||||
data object Loading : WalletListUiState
|
|
||||||
}
|
|
@ -0,0 +1,87 @@
|
|||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
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
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun TransactionCardExpanded(
|
||||||
|
transaction: Transaction,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TransactionDetail(
|
||||||
|
label: String,
|
||||||
|
value: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(label, style = MaterialTheme.typography.labelMedium, modifier = modifier)
|
||||||
|
Text(value, style = MaterialTheme.typography.bodyMedium, modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TransactionCardExpandedPreview(
|
||||||
|
@PreviewParameter(
|
||||||
|
TransactionCardPreviewParameterProvider::class,
|
||||||
|
limit = 1,
|
||||||
|
) transactions: List<Transaction>,
|
||||||
|
) {
|
||||||
|
AppTheme {
|
||||||
|
Surface {
|
||||||
|
TransactionCardExpanded(
|
||||||
|
transaction = transactions.first(),
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TransactionCardPreviewParameterProvider : PreviewParameterProvider<List<Transaction>> {
|
||||||
|
override val values = sequenceOf(PreviewParameterData.transactions)
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
|
import TransactionCardExpanded
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import im.molly.monero.demo.data.model.WalletTransaction
|
||||||
|
|
||||||
|
fun LazyListScope.transactionCardItems(
|
||||||
|
items: List<WalletTransaction>,
|
||||||
|
onTransactionClick: (txId: String, walletId: Long) -> Unit,
|
||||||
|
itemModifier: Modifier = Modifier,
|
||||||
|
) = items(
|
||||||
|
items = items,
|
||||||
|
key = { it.transaction.txId },
|
||||||
|
itemContent = {
|
||||||
|
TransactionCardExpanded(
|
||||||
|
transaction = it.transaction,
|
||||||
|
onClick = { onTransactionClick(it.transaction.txId, it.walletId) },
|
||||||
|
modifier = itemModifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
@ -0,0 +1,158 @@
|
|||||||
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import im.molly.monero.MoneroCurrency
|
||||||
|
import im.molly.monero.Transaction
|
||||||
|
import im.molly.monero.demo.ui.component.Toolbar
|
||||||
|
import im.molly.monero.demo.ui.preview.PreviewParameterData
|
||||||
|
import im.molly.monero.demo.ui.theme.AppIcons
|
||||||
|
import im.molly.monero.demo.ui.theme.AppTheme
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransactionRoute(
|
||||||
|
txId: String,
|
||||||
|
walletId: Long,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: TransactionViewModel = viewModel(
|
||||||
|
factory = TransactionViewModel.factory(txId, walletId),
|
||||||
|
key = TransactionViewModel.key(txId, walletId),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val uiState: TxUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
TransactionScreen(
|
||||||
|
uiState = uiState,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TransactionScreen(
|
||||||
|
uiState: TxUiState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(topBar = {
|
||||||
|
Toolbar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBackClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = AppIcons.ArrowBack,
|
||||||
|
contentDescription = "Back",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = "Transaction details",
|
||||||
|
)
|
||||||
|
}) { padding ->
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(padding)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(10.dp)) {
|
||||||
|
when (uiState) {
|
||||||
|
is TxUiState.Loaded -> {
|
||||||
|
val tx = uiState.transaction
|
||||||
|
TransactionDetail("State", tx.state.toString())
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Sent", tx.sent.toString())
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Received", tx.received.toString())
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Payments", tx.payments.toString())
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Time lock", tx.timeLock.toString())
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Change", MoneroCurrency.format(tx.change))
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Fee", MoneroCurrency.format(tx.fee))
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
TransactionDetail("Transaction ID", tx.txId)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TransactionDetail(
|
||||||
|
label: String,
|
||||||
|
value: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column() {
|
||||||
|
Text(label, style = MaterialTheme.typography.labelMedium, modifier = modifier)
|
||||||
|
Text(value, style = MaterialTheme.typography.bodyMedium, modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TransactionScreenPopulated(
|
||||||
|
@PreviewParameter(
|
||||||
|
TransactionScreenPreviewParameterProvider::class,
|
||||||
|
limit = 1,
|
||||||
|
) transactions: List<Transaction>,
|
||||||
|
) {
|
||||||
|
AppTheme {
|
||||||
|
TransactionScreen(
|
||||||
|
uiState = TxUiState.Loaded(transactions.first()),
|
||||||
|
onBackClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TransactionScreenError() {
|
||||||
|
AppTheme {
|
||||||
|
TransactionScreen(
|
||||||
|
uiState = TxUiState.Error,
|
||||||
|
onBackClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TransactionScreenNotFound() {
|
||||||
|
AppTheme {
|
||||||
|
TransactionScreen(
|
||||||
|
uiState = TxUiState.NotFound,
|
||||||
|
onBackClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TransactionScreenPreviewParameterProvider : PreviewParameterProvider<List<Transaction>> {
|
||||||
|
override val values = sequenceOf(PreviewParameterData.transactions)
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import im.molly.monero.Transaction
|
||||||
|
import im.molly.monero.demo.AppModule
|
||||||
|
import im.molly.monero.demo.common.Result
|
||||||
|
import im.molly.monero.demo.common.asResult
|
||||||
|
import im.molly.monero.demo.data.WalletRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class TransactionViewModel(
|
||||||
|
txId: String,
|
||||||
|
walletId: Long,
|
||||||
|
walletRepository: WalletRepository = AppModule.walletRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val uiState: StateFlow<TxUiState> =
|
||||||
|
walletRepository.getTransaction(walletId, txId)
|
||||||
|
.asResult()
|
||||||
|
.map { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> result.data?.let { tx ->
|
||||||
|
TxUiState.Loaded(tx)
|
||||||
|
} ?: TxUiState.NotFound
|
||||||
|
|
||||||
|
is Result.Error -> TxUiState.Error
|
||||||
|
is Result.Loading -> TxUiState.Loading
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = TxUiState.Loading
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun factory(txId: String, walletId: Long) = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
TransactionViewModel(txId, walletId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun key(txId: String, walletId: Long): String = "tx_$txId:$walletId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface TxUiState {
|
||||||
|
data class Loaded(val transaction: Transaction) : TxUiState
|
||||||
|
data object Error : TxUiState
|
||||||
|
data object Loading : TxUiState
|
||||||
|
data object NotFound : TxUiState
|
||||||
|
}
|
@ -3,7 +3,9 @@ package im.molly.monero.demo.ui
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -51,10 +53,12 @@ fun WalletBalanceView(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
text = "Balance at Block #${blockchainTime.height}",
|
text = "Balance at Block #${blockchainTime.height}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
BalanceRow("Confirmed", balance.confirmedAmount)
|
BalanceRow("Confirmed", balance.confirmedAmount)
|
||||||
BalanceRow("Pending", balance.pendingAmount)
|
BalanceRow("Pending", balance.pendingAmount)
|
||||||
Divider()
|
Divider()
|
||||||
@ -71,7 +75,6 @@ fun WalletBalanceView(
|
|||||||
fun BalanceRow(
|
fun BalanceRow(
|
||||||
label: String,
|
label: String,
|
||||||
amount: MoneroAmount,
|
amount: MoneroAmount,
|
||||||
format: MoneroCurrency.Format = MoneroCurrency.Format(),
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -79,8 +82,8 @@ fun BalanceRow(
|
|||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Text(text = label)
|
Text(text = label, style = MaterialTheme.typography.bodyMedium)
|
||||||
Text(text = format.format(amount))
|
Text(text = MoneroCurrency.format(amount), style = MaterialTheme.typography.bodyMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +93,6 @@ fun LockedBalanceRow(
|
|||||||
amount: MoneroAmount,
|
amount: MoneroAmount,
|
||||||
blockCount: Int,
|
blockCount: Int,
|
||||||
timeRemaining: Duration,
|
timeRemaining: Duration,
|
||||||
moneroFormat: MoneroCurrency.Format = MoneroCurrency.DefaultFormat,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -101,10 +103,10 @@ fun LockedBalanceRow(
|
|||||||
val durationText =
|
val durationText =
|
||||||
"${timeRemaining.toKotlinDuration()} ($blockCount blocks)"
|
"${timeRemaining.toKotlinDuration()} ($blockCount blocks)"
|
||||||
Column {
|
Column {
|
||||||
Text(text = label)
|
Text(text = label, style = MaterialTheme.typography.bodyMedium)
|
||||||
Text(text = durationText, fontSize = 12.sp)
|
Text(text = durationText, style = MaterialTheme.typography.bodySmall)
|
||||||
}
|
}
|
||||||
Text(text = moneroFormat.format(amount))
|
Text(text = MoneroCurrency.format(amount), style = MaterialTheme.typography.bodyMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,50 +5,81 @@ import androidx.compose.foundation.layout.Row
|
|||||||
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
|
||||||
|
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.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import im.molly.monero.Balance
|
||||||
|
import im.molly.monero.Ledger
|
||||||
|
import im.molly.monero.demo.data.model.WalletConfig
|
||||||
|
import im.molly.monero.demo.ui.theme.AppTheme
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun WalletCard(
|
fun WalletCard(
|
||||||
walletId: Long,
|
walletId: Long,
|
||||||
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: (Long) -> Unit,
|
|
||||||
viewModel: WalletViewModel = viewModel(
|
viewModel: WalletViewModel = viewModel(
|
||||||
factory = WalletViewModel.factory(walletId),
|
factory = WalletViewModel.factory(walletId),
|
||||||
key = walletId.toString(),
|
key = WalletViewModel.key(walletId),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
val walletUiState: WalletUiState by viewModel.walletUiState.collectAsStateWithLifecycle()
|
val uiState: WalletUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
onClick = { onClick(walletId) },
|
onClick = onClick, modifier = modifier.padding(8.dp)
|
||||||
modifier = modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
when (walletUiState) {
|
when (uiState) {
|
||||||
WalletUiState.Error -> {
|
is WalletUiState.Loaded -> {
|
||||||
Text(text = "Error") // TODO
|
WalletCardExpanded(
|
||||||
}
|
(uiState as WalletUiState.Loaded).config,
|
||||||
WalletUiState.Loading -> {
|
(uiState as WalletUiState.Loaded).balance,
|
||||||
Text(text = "Loading wallet...") // TODO
|
)
|
||||||
}
|
|
||||||
is WalletUiState.Success -> {
|
|
||||||
val state = walletUiState as WalletUiState.Success
|
|
||||||
Row {
|
|
||||||
Text(text = "Name: ${state.config.name}")
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
Text(text = "Ledger: ${state.ledger}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WalletUiState.Error -> WalletCardError()
|
||||||
|
WalletUiState.Loading -> WalletCardLoading()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletCardExpanded(
|
||||||
|
config: WalletConfig,
|
||||||
|
balance: Balance,
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Text(text = "Name: ${config.name}")
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Text(text = "Ledger: $balance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletCardError() {
|
||||||
|
Text(text = "Error") // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletCardLoading() {
|
||||||
|
Text(text = "Loading wallet...") // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Preview
|
||||||
|
//@Composable
|
||||||
|
//private fun WalletCardExpandedPreview() {
|
||||||
|
// AppTheme {
|
||||||
|
// Surface {
|
||||||
|
// WalletCardExpanded()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
@ -4,9 +4,9 @@ import androidx.compose.foundation.lazy.LazyListScope
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
fun LazyListScope.walletCardsItems(
|
fun LazyListScope.walletCardItems(
|
||||||
items: List<Long>,
|
items: List<Long>,
|
||||||
onItemClick: (Long) -> Unit,
|
onItemClick: (walletId: Long) -> Unit,
|
||||||
itemModifier: Modifier = Modifier,
|
itemModifier: Modifier = Modifier,
|
||||||
) = items(
|
) = items(
|
||||||
items = items,
|
items = items,
|
||||||
@ -14,7 +14,7 @@ fun LazyListScope.walletCardsItems(
|
|||||||
itemContent = {
|
itemContent = {
|
||||||
WalletCard(
|
WalletCard(
|
||||||
walletId = it,
|
walletId = it,
|
||||||
onClick = onItemClick,
|
onClick = { onItemClick(it) },
|
||||||
modifier = itemModifier,
|
modifier = itemModifier,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import im.molly.monero.demo.AppModule
|
||||||
|
import im.molly.monero.demo.data.WalletRepository
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
class WalletListViewModel(
|
||||||
|
walletRepository: WalletRepository = AppModule.walletRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val uiState: StateFlow<WalletListUiState> =
|
||||||
|
walletRepository.getWalletIdList().map { walletIds ->
|
||||||
|
if (walletIds.isNotEmpty()) {
|
||||||
|
WalletListUiState.Loaded(walletIds)
|
||||||
|
} else {
|
||||||
|
WalletListUiState.Empty
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = WalletListUiState.Loading,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface WalletListUiState {
|
||||||
|
data class Loaded(val walletIds: List<Long>) : WalletListUiState
|
||||||
|
data object Loading : WalletListUiState
|
||||||
|
data object Empty : WalletListUiState
|
||||||
|
}
|
@ -1,39 +1,46 @@
|
|||||||
package im.molly.monero.demo.ui
|
package im.molly.monero.demo.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
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.PreviewParameterProvider
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import im.molly.monero.BlockchainTime
|
|
||||||
import im.molly.monero.Ledger
|
import im.molly.monero.Ledger
|
||||||
import im.molly.monero.MoneroCurrency
|
import im.molly.monero.MoneroCurrency
|
||||||
import im.molly.monero.demo.data.model.WalletConfig
|
import im.molly.monero.demo.data.model.WalletConfig
|
||||||
import im.molly.monero.demo.ui.component.Toolbar
|
import im.molly.monero.demo.ui.component.Toolbar
|
||||||
|
import im.molly.monero.demo.ui.preview.PreviewParameterData
|
||||||
import im.molly.monero.demo.ui.theme.AppIcons
|
import im.molly.monero.demo.ui.theme.AppIcons
|
||||||
import im.molly.monero.demo.ui.theme.AppTheme
|
import im.molly.monero.demo.ui.theme.AppTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WalletRoute(
|
fun WalletRoute(
|
||||||
walletId: Long,
|
walletId: Long,
|
||||||
|
onTransactionClick: (String, Long) -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: WalletViewModel = viewModel(
|
viewModel: WalletViewModel = viewModel(
|
||||||
factory = WalletViewModel.factory(walletId),
|
factory = WalletViewModel.factory(walletId),
|
||||||
key = walletId.toString(),
|
key = WalletViewModel.key(walletId),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val walletUiState: WalletUiState by viewModel.walletUiState.collectAsStateWithLifecycle()
|
val uiState: WalletUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
WalletScreen(
|
WalletScreen(
|
||||||
uiState = walletUiState,
|
uiState = uiState,
|
||||||
onWalletConfigChange = { config -> viewModel.updateConfig(config) },
|
onWalletConfigChange = { config -> viewModel.updateConfig(config) },
|
||||||
|
onTransactionClick = onTransactionClick,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
@ -42,20 +49,113 @@ fun WalletRoute(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun WalletScreen(
|
private fun WalletScreen(
|
||||||
uiState: WalletUiState,
|
uiState: WalletUiState,
|
||||||
|
onWalletConfigChange: (WalletConfig) -> Unit,
|
||||||
|
onTransactionClick: (String, Long) -> Unit,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onWalletConfigChange: (WalletConfig) -> Unit = {},
|
|
||||||
onBackClick: () -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
when (uiState) {
|
when (uiState) {
|
||||||
WalletUiState.Error -> WalletScreenError(onBackClick = onBackClick)
|
is WalletUiState.Loaded -> WalletScreenLoaded(
|
||||||
WalletUiState.Loading -> WalletScreenLoading(onBackClick = onBackClick)
|
uiState = uiState,
|
||||||
is WalletUiState.Success -> WalletScreenPopulated(
|
|
||||||
walletConfig = uiState.config,
|
|
||||||
ledger = uiState.ledger,
|
|
||||||
onWalletConfigChange = onWalletConfigChange,
|
onWalletConfigChange = onWalletConfigChange,
|
||||||
|
onTransactionClick = onTransactionClick,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WalletUiState.Error -> WalletScreenError(onBackClick = onBackClick)
|
||||||
|
WalletUiState.Loading -> WalletScreenLoading(onBackClick = onBackClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun WalletScreenLoaded(
|
||||||
|
uiState: WalletUiState.Loaded,
|
||||||
|
onWalletConfigChange: (WalletConfig) -> Unit,
|
||||||
|
onTransactionClick: (String, Long) -> Unit,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var showRenameDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Scaffold(topBar = {
|
||||||
|
Toolbar(navigationIcon = {
|
||||||
|
IconButton(onClick = onBackClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = AppIcons.ArrowBack,
|
||||||
|
contentDescription = "Back",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, actions = {
|
||||||
|
WalletKebabMenu(
|
||||||
|
onRenameClick = { showRenameDialog = true },
|
||||||
|
onDeleteClick = { },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(style = MaterialTheme.typography.headlineLarge, text = buildAnnotatedString {
|
||||||
|
append(MoneroCurrency.SYMBOL + " ")
|
||||||
|
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||||
|
append(
|
||||||
|
MoneroCurrency.Format(precision = 5).format(uiState.balance.confirmedAmount)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Text(text = uiState.config.name, style = MaterialTheme.typography.headlineSmall)
|
||||||
|
|
||||||
|
var selectedTabIndex by rememberSaveable { mutableStateOf(0) }
|
||||||
|
|
||||||
|
WalletHeaderTabs(
|
||||||
|
titles = listOf("Balance", "Transactions"),
|
||||||
|
selectedTabIndex = selectedTabIndex,
|
||||||
|
onTabSelected = { index -> selectedTabIndex = index },
|
||||||
|
)
|
||||||
|
|
||||||
|
when (selectedTabIndex) {
|
||||||
|
0 -> {
|
||||||
|
WalletBalanceView(balance = uiState.balance, blockchainTime = uiState.blockchainTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
LazyColumn {
|
||||||
|
transactionCardItems(
|
||||||
|
items = uiState.transactions,
|
||||||
|
onTransactionClick = onTransactionClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showRenameDialog) {
|
||||||
|
var name by remember { mutableStateOf(uiState.config.name) }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRenameDialog = false },
|
||||||
|
title = { Text("Enter wallet name") },
|
||||||
|
text = {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onWalletConfigChange(uiState.config.copy(name = name))
|
||||||
|
showRenameDialog = false
|
||||||
|
}) {
|
||||||
|
Text("Rename")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,85 +171,6 @@ private fun WalletScreenLoading(
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun WalletScreenPopulated(
|
|
||||||
walletConfig: WalletConfig,
|
|
||||||
ledger: Ledger,
|
|
||||||
onWalletConfigChange: (WalletConfig) -> Unit,
|
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
var showRenameDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
Toolbar(
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBackClick) {
|
|
||||||
Icon(
|
|
||||||
imageVector = AppIcons.ArrowBack,
|
|
||||||
contentDescription = "Back",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
WalletKebabMenu(
|
|
||||||
onRenameClick = { showRenameDialog = true },
|
|
||||||
onDeleteClick = { },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { padding ->
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
|
||||||
text = buildAnnotatedString {
|
|
||||||
append(MoneroCurrency.SYMBOL + " ")
|
|
||||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
||||||
append(MoneroCurrency.Format(precision = 5).format(ledger.balance.confirmedAmount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Text(text = walletConfig.name, style = MaterialTheme.typography.headlineSmall)
|
|
||||||
|
|
||||||
WalletBalanceView(
|
|
||||||
balance = ledger.balance,
|
|
||||||
blockchainTime = ledger.checkedAt,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showRenameDialog) {
|
|
||||||
var name by remember { mutableStateOf(walletConfig.name) }
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showRenameDialog = false },
|
|
||||||
title = { Text("Enter wallet name") },
|
|
||||||
text = {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = name,
|
|
||||||
onValueChange = { name = it },
|
|
||||||
singleLine = true,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
onWalletConfigChange(walletConfig.copy(name = name))
|
|
||||||
showRenameDialog = false
|
|
||||||
}) {
|
|
||||||
Text("Rename")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WalletKebabMenu(
|
private fun WalletKebabMenu(
|
||||||
onRenameClick: () -> Unit,
|
onRenameClick: () -> Unit,
|
||||||
@ -184,26 +205,57 @@ private fun WalletKebabMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletHeaderTabs(
|
||||||
|
titles: List<String>,
|
||||||
|
selectedTabIndex: Int,
|
||||||
|
onTabSelected: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
TabRow(selectedTabIndex = selectedTabIndex) {
|
||||||
|
titles.forEachIndexed { index, title ->
|
||||||
|
Tab(
|
||||||
|
selected = index == selectedTabIndex,
|
||||||
|
onClick = { onTabSelected(index) },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun WalletScreenPreview() {
|
private fun WalletScreenPopulated(
|
||||||
|
@PreviewParameter(WalletScreenPreviewParameterProvider::class) ledger: Ledger,
|
||||||
|
) {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
WalletScreen(
|
WalletScreen(
|
||||||
uiState = WalletUiState.Success(
|
uiState = WalletUiState.Loaded(
|
||||||
WalletConfig(
|
config = WalletConfig(
|
||||||
id = 0,
|
id = 0,
|
||||||
publicAddress = "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
|
publicAddress = ledger.primaryAddress,
|
||||||
filename = "",
|
filename = "",
|
||||||
name = "Personal",
|
name = "Personal",
|
||||||
remoteNodes = emptySet(),
|
remoteNodes = emptySet(),
|
||||||
),
|
),
|
||||||
Ledger(
|
balance = ledger.balance,
|
||||||
publicAddress = "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
|
blockchainTime = ledger.checkedAt,
|
||||||
transactions = emptyMap(),
|
transactions = emptyList(),
|
||||||
enotes = emptySet(),
|
|
||||||
checkedAt = BlockchainTime.Genesis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
onWalletConfigChange = {},
|
||||||
|
onTransactionClick = { _: String, _: Long -> },
|
||||||
|
onBackClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class WalletScreenPreviewParameterProvider : PreviewParameterProvider<Ledger> {
|
||||||
|
override val values = sequenceOf(PreviewParameterData.ledger)
|
||||||
|
}
|
||||||
|
@ -4,21 +4,23 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import im.molly.monero.Ledger
|
import im.molly.monero.Balance
|
||||||
|
import im.molly.monero.BlockchainTime
|
||||||
import im.molly.monero.demo.AppModule
|
import im.molly.monero.demo.AppModule
|
||||||
import im.molly.monero.demo.common.Result
|
import im.molly.monero.demo.common.Result
|
||||||
import im.molly.monero.demo.common.asResult
|
import im.molly.monero.demo.common.asResult
|
||||||
import im.molly.monero.demo.data.WalletRepository
|
import im.molly.monero.demo.data.WalletRepository
|
||||||
import im.molly.monero.demo.data.model.WalletConfig
|
import im.molly.monero.demo.data.model.WalletConfig
|
||||||
|
import im.molly.monero.demo.data.model.WalletTransaction
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class WalletViewModel(
|
class WalletViewModel(
|
||||||
private val walletId: Long,
|
walletId: Long,
|
||||||
private val walletRepository: WalletRepository = AppModule.walletRepository,
|
private val walletRepository: WalletRepository = AppModule.walletRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val walletUiState: StateFlow<WalletUiState> = walletUiState(
|
val uiState: StateFlow<WalletUiState> = walletUiState(
|
||||||
walletId = walletId,
|
walletId = walletId,
|
||||||
walletRepository = walletRepository,
|
walletRepository = walletRepository,
|
||||||
).stateIn(
|
).stateIn(
|
||||||
@ -33,6 +35,8 @@ class WalletViewModel(
|
|||||||
WalletViewModel(walletId)
|
WalletViewModel(walletId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun key(walletId: Long): String = "wallet_$walletId"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfig(config: WalletConfig) {
|
fun updateConfig(config: WalletConfig) {
|
||||||
@ -53,11 +57,19 @@ private fun walletUiState(
|
|||||||
).asResult().map { result ->
|
).asResult().map { result ->
|
||||||
when (result) {
|
when (result) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
WalletUiState.Success(result.data.first, result.data.second)
|
val config = result.data.first
|
||||||
|
val ledger = result.data.second
|
||||||
|
val balance = ledger.balance
|
||||||
|
val blockchainTime = ledger.checkedAt
|
||||||
|
val transactions =
|
||||||
|
ledger.transactions.map { WalletTransaction(config.id, it.value) }
|
||||||
|
WalletUiState.Loaded(config, blockchainTime, balance, transactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Result.Loading -> {
|
is Result.Loading -> {
|
||||||
WalletUiState.Loading
|
WalletUiState.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
is Result.Error -> {
|
is Result.Error -> {
|
||||||
WalletUiState.Error
|
WalletUiState.Error
|
||||||
}
|
}
|
||||||
@ -66,9 +78,11 @@ private fun walletUiState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface WalletUiState {
|
sealed interface WalletUiState {
|
||||||
data class Success(
|
data class Loaded(
|
||||||
val config: WalletConfig,
|
val config: WalletConfig,
|
||||||
val ledger: Ledger,
|
val blockchainTime: BlockchainTime,
|
||||||
|
val balance: Balance,
|
||||||
|
val transactions: List<WalletTransaction>,
|
||||||
) : WalletUiState
|
) : WalletUiState
|
||||||
|
|
||||||
data object Error : WalletUiState
|
data object Error : WalletUiState
|
||||||
|
@ -12,8 +12,12 @@ fun NavController.navigateToHistory(navOptions: NavOptions? = null) {
|
|||||||
navigate(historyNavRoute, navOptions)
|
navigate(historyNavRoute, navOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NavGraphBuilder.historyScreen() {
|
fun NavGraphBuilder.historyScreen(
|
||||||
|
navigateToTransaction: (String, Long) -> Unit,
|
||||||
|
) {
|
||||||
composable(route = historyNavRoute) {
|
composable(route = historyNavRoute) {
|
||||||
HistoryRoute()
|
HistoryRoute(
|
||||||
|
navigateToTransaction = navigateToTransaction,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,23 @@ fun NavGraph(
|
|||||||
navController.navigateToAddWalletWizardGraph()
|
navController.navigateToAddWalletWizardGraph()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
historyScreen()
|
historyScreen(
|
||||||
|
navigateToTransaction = { txId, walletId ->
|
||||||
|
navController.navigateToTransaction(txId, walletId)
|
||||||
|
},
|
||||||
|
)
|
||||||
settingsScreen(
|
settingsScreen(
|
||||||
navigateToEditRemoteNode = { remoteNodeId ->
|
navigateToEditRemoteNode = { remoteNodeId ->
|
||||||
navController.navigateToEditRemoteNode(remoteNodeId)
|
navController.navigateToEditRemoteNode(remoteNodeId)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
walletScreen(
|
walletScreen(
|
||||||
|
navigateToTransaction = { txId, walletId ->
|
||||||
|
navController.navigateToTransaction(txId, walletId)
|
||||||
|
},
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
)
|
||||||
|
transactionScreen(
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
)
|
)
|
||||||
editRemoteNodeDialog(
|
editRemoteNodeDialog(
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package im.molly.monero.demo.ui.navigation
|
||||||
|
|
||||||
|
import androidx.navigation.*
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import im.molly.monero.demo.ui.TransactionRoute
|
||||||
|
|
||||||
|
const val transactionNavRoute = "tx"
|
||||||
|
|
||||||
|
private const val txIdArg = "txId"
|
||||||
|
private const val walletIdArg = "walletId"
|
||||||
|
|
||||||
|
fun NavController.navigateToTransaction(txId: String, walletId: Long) {
|
||||||
|
val route = "$transactionNavRoute/$txId/$walletId"
|
||||||
|
navigate(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.transactionScreen(
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
composable(
|
||||||
|
route = "$transactionNavRoute/{$txIdArg}/{$walletIdArg}",
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(txIdArg) { type = NavType.StringType },
|
||||||
|
navArgument(walletIdArg) { type = NavType.LongType },
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val arguments = requireNotNull(it.arguments)
|
||||||
|
val txId = arguments.getString(txIdArg)
|
||||||
|
val walletId = arguments.getLong(walletIdArg)
|
||||||
|
TransactionRoute(
|
||||||
|
txId = txId!!,
|
||||||
|
walletId = walletId,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ fun NavController.navigateToAddWalletSecondStep(restoreWallet: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun NavGraphBuilder.walletScreen(
|
fun NavGraphBuilder.walletScreen(
|
||||||
|
navigateToTransaction: (String, Long) -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
composable(
|
composable(
|
||||||
@ -44,6 +45,7 @@ fun NavGraphBuilder.walletScreen(
|
|||||||
val walletId = arguments.getLong(walletIdArg)
|
val walletId = arguments.getLong(walletIdArg)
|
||||||
WalletRoute(
|
WalletRoute(
|
||||||
walletId = walletId,
|
walletId = walletId,
|
||||||
|
onTransactionClick = navigateToTransaction,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package im.molly.monero.demo.ui.preview
|
||||||
|
|
||||||
|
import im.molly.monero.BlockHeader
|
||||||
|
import im.molly.monero.BlockchainTime
|
||||||
|
import im.molly.monero.HashDigest
|
||||||
|
import im.molly.monero.Ledger
|
||||||
|
import im.molly.monero.MoneroAmount
|
||||||
|
import im.molly.monero.PaymentDetail
|
||||||
|
import im.molly.monero.PublicAddress
|
||||||
|
import im.molly.monero.Transaction
|
||||||
|
import im.molly.monero.TxState
|
||||||
|
import im.molly.monero.xmr
|
||||||
|
|
||||||
|
object PreviewParameterData {
|
||||||
|
val recipients =
|
||||||
|
listOf(PublicAddress.parse("888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H"))
|
||||||
|
|
||||||
|
val transactions = listOf(
|
||||||
|
Transaction(
|
||||||
|
hash = HashDigest("e7a60483591378d536792d070f2bf6ccb7d0666df03b57f485ddaf66899a294b"),
|
||||||
|
state = TxState.OnChain(BlockHeader(height = 2999840, epochSecond = 1697792826)),
|
||||||
|
timeLock = BlockchainTime.Block(2999850),
|
||||||
|
sent = emptySet(),
|
||||||
|
received = emptySet(),
|
||||||
|
payments = listOf(PaymentDetail((0.10).xmr, recipients.first())),
|
||||||
|
fee = 0.00093088.xmr,
|
||||||
|
change = MoneroAmount.ZERO,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val ledger = Ledger(
|
||||||
|
primaryAddress = "4AYjQM9HoAFNUeC3cvSfgeAN89oMMpMqiByvunzSzhn97cj726rJj3x8hCbH58UnMqQJShczCxbpWRiCJQ3HCUDHLiKuo4T",
|
||||||
|
checkedAt = BlockchainTime.Block(2999840),
|
||||||
|
enotes = emptySet(),
|
||||||
|
transactions = transactions.associateBy { it.txId },
|
||||||
|
)
|
||||||
|
}
|
@ -6,14 +6,18 @@ data class AccountAddress(
|
|||||||
val subAddressIndex: Int = 0,
|
val subAddressIndex: Int = 0,
|
||||||
) : PublicAddress by publicAddress {
|
) : PublicAddress by publicAddress {
|
||||||
|
|
||||||
|
val isPrimaryAddress: Boolean
|
||||||
|
get() = accountIndex == 0 && subAddressIndex == 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
when (publicAddress) {
|
when (publicAddress) {
|
||||||
is SubAddress -> require(accountIndex != -1 && subAddressIndex != -1)
|
is StandardAddress -> require(isPrimaryAddress) {
|
||||||
else -> require(accountIndex == 0 && subAddressIndex == 0)
|
"Standard addresses must have subaddress indices set to zero"
|
||||||
|
}
|
||||||
|
is SubAddress -> require(accountIndex != -1 && subAddressIndex != -1) {
|
||||||
|
"Invalid subaddress indices"
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("Unsupported address type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun belongsTo(targetAccountIndex: Int): Boolean {
|
|
||||||
return accountIndex == targetAccountIndex
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ class Enote(
|
|||||||
val sourceTxId: String?,
|
val sourceTxId: String?,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
require(age >= 0) { "Enote age $age must not be negative" }
|
require(amount > 0) { "Amount must be greater than 0" }
|
||||||
|
require(age >= 0) { "Age cannot be negative" }
|
||||||
}
|
}
|
||||||
|
|
||||||
var spent: Boolean = false
|
var spent: Boolean = false
|
||||||
|
@ -3,7 +3,7 @@ package im.molly.monero
|
|||||||
//import im.molly.monero.proto.LedgerProto
|
//import im.molly.monero.proto.LedgerProto
|
||||||
|
|
||||||
data class Ledger(
|
data class Ledger(
|
||||||
val publicAddress: String,
|
val primaryAddress: String,
|
||||||
val transactions: Map<String, Transaction>,
|
val transactions: Map<String, Transaction>,
|
||||||
val enotes: Set<TimeLocked<Enote>>,
|
val enotes: Set<TimeLocked<Enote>>,
|
||||||
val checkedAt: BlockchainTime,
|
val checkedAt: BlockchainTime,
|
||||||
|
@ -15,7 +15,8 @@ value class MoneroAmount(val atomicUnits: Long) : Parcelable {
|
|||||||
val ZERO = MoneroAmount(0)
|
val ZERO = MoneroAmount(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toXmr(): BigDecimal = BigDecimal.valueOf(atomicUnits, ATOMIC_UNIT_SCALE)
|
val xmr: BigDecimal
|
||||||
|
get() = BigDecimal.valueOf(atomicUnits, ATOMIC_UNIT_SCALE)
|
||||||
|
|
||||||
override fun toString() = atomicUnits.toString()
|
override fun toString() = atomicUnits.toString()
|
||||||
|
|
||||||
|
@ -8,10 +8,8 @@ object MoneroCurrency {
|
|||||||
|
|
||||||
const val MAX_PRECISION = MoneroAmount.ATOMIC_UNIT_SCALE
|
const val MAX_PRECISION = MoneroAmount.ATOMIC_UNIT_SCALE
|
||||||
|
|
||||||
val DefaultFormat = Format()
|
open class Format(
|
||||||
|
val precision: Int,
|
||||||
data class Format(
|
|
||||||
val precision: Int = MAX_PRECISION,
|
|
||||||
val locale: Locale = Locale.US,
|
val locale: Locale = Locale.US,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
@ -24,10 +22,36 @@ object MoneroCurrency {
|
|||||||
minimumFractionDigits = precision
|
minimumFractionDigits = precision
|
||||||
}
|
}
|
||||||
|
|
||||||
fun format(amount: MoneroAmount): String = numberFormat.format(amount.toXmr())
|
open fun format(amount: MoneroAmount): String {
|
||||||
|
return numberFormat.format(amount.xmr)
|
||||||
|
}
|
||||||
|
|
||||||
fun parse(source: String): MoneroAmount {
|
open fun parse(source: String): MoneroAmount {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ExactFormat = object : Format(MoneroAmount.ATOMIC_UNIT_SCALE) {
|
||||||
|
override fun format(amount: MoneroAmount) = buildString {
|
||||||
|
val num = amount.atomicUnits.toString()
|
||||||
|
|
||||||
|
if (precision < num.length) {
|
||||||
|
val point = num.length - precision
|
||||||
|
append(num.substring(0, point))
|
||||||
|
append('.')
|
||||||
|
append(num.substring(point))
|
||||||
|
} else {
|
||||||
|
append("0.")
|
||||||
|
for (i in 1..(precision - num.length)) {
|
||||||
|
append('0')
|
||||||
|
}
|
||||||
|
append(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun format(amount: MoneroAmount, outputFormat: Format = ExactFormat): String {
|
||||||
|
return outputFormat.format(amount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ package im.molly.monero
|
|||||||
|
|
||||||
import im.molly.monero.util.decodeBase58
|
import im.molly.monero.util.decodeBase58
|
||||||
|
|
||||||
interface PublicAddress {
|
sealed interface PublicAddress {
|
||||||
|
val address: String
|
||||||
val network: MoneroNetwork
|
val network: MoneroNetwork
|
||||||
val subAddress: Boolean
|
val subAddress: Boolean
|
||||||
// viewPublicKey: ByteArray
|
// viewPublicKey: ByteArray
|
||||||
@ -19,19 +20,18 @@ interface PublicAddress {
|
|||||||
throw InvalidAddress("Address too short")
|
throw InvalidAddress("Address too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefix = decoded[0].toLong()
|
return when (val prefix = decoded[0].toLong()) {
|
||||||
|
in StandardAddress.prefixes -> {
|
||||||
StandardAddress.prefixes[prefix]?.let { network ->
|
StandardAddress(publicAddress, StandardAddress.prefixes[prefix]!!)
|
||||||
return StandardAddress(network)
|
}
|
||||||
|
in SubAddress.prefixes -> {
|
||||||
|
SubAddress(publicAddress, SubAddress.prefixes[prefix]!!)
|
||||||
|
}
|
||||||
|
in IntegratedAddress.prefixes -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
else -> throw InvalidAddress("Unrecognized address prefix")
|
||||||
}
|
}
|
||||||
SubAddress.prefixes[prefix]?.let { network ->
|
|
||||||
return SubAddress(network)
|
|
||||||
}
|
|
||||||
IntegratedAddress.prefixes[prefix]?.let { network ->
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
throw InvalidAddress("Unrecognized address prefix")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,6 +39,7 @@ interface PublicAddress {
|
|||||||
class InvalidAddress(message: String, cause: Throwable? = null) : Exception(message, cause)
|
class InvalidAddress(message: String, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
|
||||||
data class StandardAddress(
|
data class StandardAddress(
|
||||||
|
override val address: String,
|
||||||
override val network: MoneroNetwork,
|
override val network: MoneroNetwork,
|
||||||
) : PublicAddress {
|
) : PublicAddress {
|
||||||
override val subAddress = false
|
override val subAddress = false
|
||||||
@ -50,9 +51,12 @@ data class StandardAddress(
|
|||||||
24L to MoneroNetwork.Stagenet,
|
24L to MoneroNetwork.Stagenet,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = address
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SubAddress(
|
data class SubAddress(
|
||||||
|
override val address: String,
|
||||||
override val network: MoneroNetwork,
|
override val network: MoneroNetwork,
|
||||||
) : PublicAddress {
|
) : PublicAddress {
|
||||||
override val subAddress = true
|
override val subAddress = true
|
||||||
@ -64,9 +68,12 @@ data class SubAddress(
|
|||||||
36L to MoneroNetwork.Stagenet,
|
36L to MoneroNetwork.Stagenet,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = address
|
||||||
}
|
}
|
||||||
|
|
||||||
data class IntegratedAddress(
|
data class IntegratedAddress(
|
||||||
|
override val address: String,
|
||||||
override val network: MoneroNetwork,
|
override val network: MoneroNetwork,
|
||||||
val paymentId: Long,
|
val paymentId: Long,
|
||||||
) : PublicAddress {
|
) : PublicAddress {
|
||||||
@ -79,4 +86,6 @@ data class IntegratedAddress(
|
|||||||
25L to MoneroNetwork.Stagenet,
|
25L to MoneroNetwork.Stagenet,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = address
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user