Phase 3: Wallet panel UI and full /pay flow wiring
- Add WalletPanelView with 4 tabs (Overview, Assets, History, Settings) - Overview tab shows balance, QR code for receiving, and Send ADA button - Assets tab shows native tokens held at address - History tab shows recent transactions with explorer links - Settings tab shows address, network, and backup/delete options - Add NativeAsset and TxSummary models to wallet API - Add getAddressAssets() and getAddressTransactions() to CardanoClient - Implement new methods in KoiosCardanoClient and FakeCardanoClient - Add wallet button to MessagesViewTopBar (DM rooms only) - Add isDmRoom to MessagesState for conditional UI - Wire navigateToWallet() callback through to MessagesFlowNode - Add NavTarget.WalletPanel and WalletPanelNode integration - Add string resources for wallet panel UI Known limitations: - Uses Chart icon as placeholder for wallet (Compound lacks wallet icon) - Wallet setup flow not implemented (TODO) - Transaction amounts in history need additional API calls to calculate
This commit is contained in:
parent
b867fa783e
commit
e33c87c164
24 changed files with 1685 additions and 1 deletions
|
|
@ -53,4 +53,21 @@ interface CardanoClient {
|
|||
* @return Current [ProtocolParameters] from the latest epoch
|
||||
*/
|
||||
suspend fun getProtocolParameters(): Result<ProtocolParameters>
|
||||
|
||||
/**
|
||||
* Get native assets (tokens) for a given address.
|
||||
*
|
||||
* @param address Bech32 Cardano address
|
||||
* @return List of [NativeAsset] objects
|
||||
*/
|
||||
suspend fun getAddressAssets(address: String): Result<List<NativeAsset>>
|
||||
|
||||
/**
|
||||
* Get transaction history for a given address.
|
||||
*
|
||||
* @param address Bech32 Cardano address
|
||||
* @param limit Maximum number of transactions to return (default 20)
|
||||
* @return List of [TxSummary] objects, most recent first
|
||||
*/
|
||||
suspend fun getAddressTransactions(address: String, limit: Int = 20): Result<List<TxSummary>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
/**
|
||||
* Represents a native asset (token) on Cardano.
|
||||
*
|
||||
* @property policyId The minting policy ID (hex)
|
||||
* @property assetName The asset name (hex or decoded)
|
||||
* @property quantity The amount of this asset
|
||||
* @property displayName Human-readable name if available
|
||||
* @property fingerprint The asset fingerprint (CIP-14)
|
||||
*/
|
||||
data class NativeAsset(
|
||||
val policyId: String,
|
||||
val assetName: String,
|
||||
val quantity: Long,
|
||||
val displayName: String?,
|
||||
val fingerprint: String?,
|
||||
) {
|
||||
/**
|
||||
* Truncated policy ID for display.
|
||||
*/
|
||||
val truncatedPolicyId: String
|
||||
get() = if (policyId.length > 16) {
|
||||
"${policyId.take(8)}...${policyId.takeLast(8)}"
|
||||
} else {
|
||||
policyId
|
||||
}
|
||||
|
||||
/**
|
||||
* Display name, falling back to truncated asset name.
|
||||
*/
|
||||
val name: String
|
||||
get() = displayName ?: assetName.takeIf { it.isNotEmpty() }?.let {
|
||||
// Try to decode hex to ASCII if it looks printable
|
||||
try {
|
||||
val decoded = it.chunked(2).map { hex -> hex.toInt(16).toChar() }.joinToString("")
|
||||
if (decoded.all { c -> c.isLetterOrDigit() || c in " -_" }) decoded else it
|
||||
} catch (_: Exception) {
|
||||
it
|
||||
}
|
||||
} ?: "Unknown"
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* Summary of a Cardano transaction for history display.
|
||||
*
|
||||
* @property txHash The transaction hash
|
||||
* @property blockTime Unix timestamp when the tx was included in a block
|
||||
* @property totalOutput Total output in lovelace
|
||||
* @property fee Transaction fee in lovelace
|
||||
* @property direction Whether this was sent or received
|
||||
*/
|
||||
data class TxSummary(
|
||||
val txHash: String,
|
||||
val blockTime: Long,
|
||||
val totalOutput: Long,
|
||||
val fee: Long,
|
||||
val direction: Direction,
|
||||
) {
|
||||
enum class Direction {
|
||||
SENT,
|
||||
RECEIVED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted date for display.
|
||||
*/
|
||||
val formattedDate: String
|
||||
get() = try {
|
||||
val instant = Instant.ofEpochSecond(blockTime)
|
||||
val formatter = DateTimeFormatter.ofPattern("MMM d, yyyy")
|
||||
.withZone(ZoneId.systemDefault())
|
||||
formatter.format(instant)
|
||||
} catch (_: Exception) {
|
||||
"Unknown date"
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncated tx hash for display.
|
||||
*/
|
||||
val truncatedTxHash: String
|
||||
get() = if (txHash.length > 16) {
|
||||
"${txHash.take(8)}...${txHash.takeLast(8)}"
|
||||
} else {
|
||||
txHash
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount formatted as ADA.
|
||||
*/
|
||||
val amountAda: String
|
||||
get() {
|
||||
val ada = totalOutput / 1_000_000.0
|
||||
return if (ada == ada.toLong().toDouble()) {
|
||||
"${ada.toLong()} ADA"
|
||||
} else {
|
||||
val formatted = "%.6f".format(ada).trimEnd('0').trimEnd('.')
|
||||
"$formatted ADA"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explorer URL for this transaction.
|
||||
*/
|
||||
fun explorerUrl(isTestnet: Boolean): String {
|
||||
return if (isTestnet) {
|
||||
"https://preprod.cardanoscan.io/transaction/$txHash"
|
||||
} else {
|
||||
"https://cardanoscan.io/transaction/$txHash"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue