mint info screen, wip

This commit is contained in:
callebtc
2025-07-16 14:28:38 +02:00
parent 7fc623d65a
commit 198a8ba6c5
11 changed files with 1429 additions and 178 deletions
@@ -2,6 +2,14 @@ package com.bitchat.android.wallet.data
import java.util.Date import java.util.Date
/**
* Represents contact information for a mint
*/
data class ContactInfo(
val method: String, // e.g., "email", "twitter", "nostr"
val info: String // e.g., "contact@example.com", "@username", "npub..."
)
/** /**
* Represents a Cashu mint information * Represents a Cashu mint information
*/ */
@@ -10,9 +18,9 @@ data class MintInfo(
val name: String, val name: String,
val description: String? = null, val description: String? = null,
val descriptionLong: String? = null, val descriptionLong: String? = null,
val contact: String? = null, val contact: List<ContactInfo>? = null,
val version: String, val version: String,
val nuts: Map<String, String>, // Simplified - was Map<String, Any> val nuts: Map<String, Any>? = null, // NUT specifications supported by the mint
val motd: String? = null, val motd: String? = null,
val icon: String? = null, val icon: String? = null,
val time: Date? = null val time: Date? = null
@@ -21,7 +21,6 @@ class CashuService {
private var currentMintUrl: String? = null private var currentMintUrl: String? = null
private var currentUnit: String = "sat" private var currentUnit: String = "sat"
private var isInitialized = false private var isInitialized = false
private var isCdkAvailable = false
private var repository: WalletRepository? = null private var repository: WalletRepository? = null
companion object { companion object {
@@ -160,38 +159,6 @@ class CashuService {
} }
} }
/**
* Check if CDK is available and initialize appropriately
*/
private fun initializeCdkAvailability(): Boolean {
if (isCdkAvailable) return true
Log.d(TAG, "=== CDK LIBRARY AVAILABILITY CHECK ===")
try {
// Test CDK availability by calling a simple function
val testMnemonic = generateMnemonic()
Log.d(TAG, "✅ CDK library is fully available and functional!")
Log.d(TAG, "Generated test mnemonic with ${testMnemonic.split(" ").size} words")
isCdkAvailable = true
return true
} catch (e: UnsatisfiedLinkError) {
Log.w(TAG, "❌ CDK library not available: ${e.message}")
} catch (e: FfiException) {
Log.w(TAG, "❌ CDK FFI error: ${e.message}")
} catch (e: NoClassDefFoundError) {
Log.w(TAG, "❌ CDK classes not found: ${e.message}")
} catch (e: Exception) {
Log.w(TAG, "❌ Unexpected CDK error: ${e.message}")
}
isCdkAvailable = false
Log.w(TAG, "⚠️ CDK library not available - wallet will not function properly")
Log.d(TAG, "=== END CDK LIBRARY CHECK ===")
return false
}
/** /**
* Initialize the CDK wallet with a mint URL * Initialize the CDK wallet with a mint URL
*/ */
@@ -200,11 +167,6 @@ class CashuService {
try { try {
Log.d(TAG, "Initializing wallet with mint: $mintUrl") Log.d(TAG, "Initializing wallet with mint: $mintUrl")
// Test CDK availability first
if (!initializeCdkAvailability()) {
return@withContext Result.failure(Exception("CDK library not available"))
}
// Create database path using application context // Create database path using application context
val context = getApplicationContext() val context = getApplicationContext()
val dbPath = "${context.filesDir}/cashu_wallet.db" val dbPath = "${context.filesDir}/cashu_wallet.db"
@@ -269,7 +231,7 @@ class CashuService {
initializeWallet(mintUrl).getOrThrow() initializeWallet(mintUrl).getOrThrow()
} }
val mintInfo = if (isCdkAvailable && wallet != null) { val mintInfo = if (wallet != null) {
try { try {
// Get real mint info from CDK // Get real mint info from CDK
val mintInfoData = wallet!!.getMintInfo() val mintInfoData = wallet!!.getMintInfo()
@@ -280,7 +242,7 @@ class CashuService {
name = extractMintName(mintUrl), name = extractMintName(mintUrl),
description = "Cashu mint via CDK", description = "Cashu mint via CDK",
descriptionLong = "Real Cashu mint connection via CDK-FFI", descriptionLong = "Real Cashu mint connection via CDK-FFI",
contact = "", contact = emptyList(),
version = "1.0.0", version = "1.0.0",
nuts = mapOf( nuts = mapOf(
"4" to "true", // NUT-4: Mint quote "4" to "true", // NUT-4: Mint quote
@@ -318,7 +280,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -349,7 +311,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -446,7 +408,7 @@ class CashuService {
) )
// Now receive the token with the properly initialized wallet // Now receive the token with the properly initialized wallet
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -476,7 +438,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -527,7 +489,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -574,7 +536,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -619,7 +581,7 @@ class CashuService {
initializeWallet(DEFAULT_MINT_URL).getOrThrow() initializeWallet(DEFAULT_MINT_URL).getOrThrow()
} }
if (!isCdkAvailable || wallet == null) { if (wallet == null) {
return@withContext Result.failure(Exception("CDK not available")) return@withContext Result.failure(Exception("CDK not available"))
} }
@@ -693,14 +655,6 @@ class CashuService {
} }
} }
/**
* Check if real CDK is available
*/
fun isCdkAvailable(): Boolean {
initializeCdkAvailability()
return isCdkAvailable
}
// Helper functions // Helper functions
private fun getApplicationContext(): Context { private fun getApplicationContext(): Context {
@@ -25,6 +25,7 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.bitchat.android.wallet.data.Mint import com.bitchat.android.wallet.data.Mint
import com.bitchat.android.wallet.viewmodel.WalletViewModel import com.bitchat.android.wallet.viewmodel.WalletViewModel
import com.bitchat.android.wallet.ui.mintinfo.MintDetailsScreen
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@@ -41,7 +42,41 @@ fun MintsScreen(
val showAddMintDialog by viewModel.showAddMintDialog.observeAsState(false) val showAddMintDialog by viewModel.showAddMintDialog.observeAsState(false)
val isLoading by viewModel.isLoading.observeAsState(false) val isLoading by viewModel.isLoading.observeAsState(false)
val errorMessage by viewModel.errorMessage.observeAsState() val errorMessage by viewModel.errorMessage.observeAsState()
val showMintDetails by viewModel.showMintDetails.observeAsState(false)
val selectedMintUrl by viewModel.selectedMintUrl.observeAsState()
// Show mint details screen if selected
if (showMintDetails && selectedMintUrl != null) {
MintDetailsScreen(
mintUrl = selectedMintUrl!!,
walletViewModel = viewModel,
onNavigateBack = { viewModel.hideMintDetails() },
modifier = modifier
)
} else {
// Show normal mints list
MintsListContent(
viewModel = viewModel,
mints = mints,
activeMint = activeMint,
showAddMintDialog = showAddMintDialog,
isLoading = isLoading,
errorMessage = errorMessage,
modifier = modifier
)
}
}
@Composable
private fun MintsListContent(
viewModel: WalletViewModel,
mints: List<Mint>,
activeMint: String?,
showAddMintDialog: Boolean,
isLoading: Boolean,
errorMessage: String?,
modifier: Modifier = Modifier
) {
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -89,14 +124,14 @@ fun MintsScreen(
if (mints.isEmpty()) { if (mints.isEmpty()) {
EmptyMintsCard(onAddClick = { viewModel.showAddMintDialog() }) EmptyMintsCard(onAddClick = { viewModel.showAddMintDialog() })
} else { } else {
MintsList( MintsList(
mints = mints, mints = mints,
activeMint = activeMint, activeMint = activeMint,
onMintSelect = { viewModel.setActiveMint(it) }, onMintSelect = { viewModel.setActiveMint(it) },
onMintEdit = { mint, newNickname -> onMintInfo = { mintUrl ->
viewModel.updateMintNickname(mint, newNickname) viewModel.showMintDetails(mintUrl)
} }
) )
} }
// Error message // Error message
@@ -123,7 +158,7 @@ private fun MintsList(
mints: List<Mint>, mints: List<Mint>,
activeMint: String?, activeMint: String?,
onMintSelect: (String) -> Unit, onMintSelect: (String) -> Unit,
onMintEdit: (String, String) -> Unit onMintInfo: (String) -> Unit
) { ) {
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -133,7 +168,7 @@ private fun MintsList(
mint = mint, mint = mint,
isActive = mint.url == activeMint, isActive = mint.url == activeMint,
onSelect = { onMintSelect(mint.url) }, onSelect = { onMintSelect(mint.url) },
onEdit = { newNickname -> onMintEdit(mint.url, newNickname) } onInfo = { onMintInfo(mint.url) }
) )
} }
} }
@@ -144,9 +179,8 @@ private fun MintItem(
mint: Mint, mint: Mint,
isActive: Boolean, isActive: Boolean,
onSelect: () -> Unit, onSelect: () -> Unit,
onEdit: (String) -> Unit onInfo: () -> Unit
) { ) {
var showEditDialog by remember { mutableStateOf(false) }
Card( Card(
modifier = Modifier modifier = Modifier
@@ -210,11 +244,11 @@ private fun MintItem(
) )
} }
// Edit button // Info button
IconButton(onClick = { showEditDialog = true }) { IconButton(onClick = onInfo) {
Icon( Icon(
imageVector = Icons.Filled.Edit, imageVector = Icons.Filled.Info,
contentDescription = "Edit", contentDescription = "Mint Details",
tint = Color.Gray, tint = Color.Gray,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
@@ -232,17 +266,7 @@ private fun MintItem(
} }
} }
// Edit dialog
if (showEditDialog) {
EditMintDialog(
currentNickname = mint.nickname,
onSave = {
onEdit(it)
showEditDialog = false
},
onDismiss = { showEditDialog = false }
)
}
} }
@Composable @Composable
@@ -436,100 +460,7 @@ private fun AddMintDialog(
} }
} }
@Composable
private fun EditMintDialog(
currentNickname: String,
onSave: (String) -> Unit,
onDismiss: () -> Unit
) {
var nickname by remember { mutableStateOf(currentNickname) }
Dialog(onDismissRequest = onDismiss) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Column(
modifier = Modifier.padding(24.dp)
) {
Text(
text = "Edit Mint Nickname",
color = Color.White,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = nickname,
onValueChange = { nickname = it },
label = {
Text(
text = "Nickname",
fontFamily = FontFamily.Monospace
)
},
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFF00C851),
focusedLabelColor = Color(0xFF00C851),
unfocusedTextColor = Color.White,
focusedTextColor = Color.White
),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Cancel button
Button(
onClick = onDismiss,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
border = androidx.compose.foundation.BorderStroke(
1.dp,
Color.Gray
),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = "Cancel",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
}
// Save button
Button(
onClick = { onSave(nickname) },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851)
),
shape = RoundedCornerShape(8.dp),
enabled = nickname.isNotEmpty()
) {
Text(
text = "Save",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
}
}
}
}
}
}
@Composable @Composable
private fun ErrorCard( private fun ErrorCard(
@@ -0,0 +1,139 @@
package com.bitchat.android.wallet.ui.mintinfo
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bitchat.android.wallet.data.Mint
/**
* Action buttons section for mint operations
*/
@Composable
fun MintActionButtons(
mint: Mint,
onEditNickname: () -> Unit,
onCopyUrl: () -> Unit,
onDeleteMint: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Section divider and title
SectionDivider(title = "ACTIONS")
// Action buttons container
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Edit nickname action
ActionButton(
icon = Icons.Filled.Edit,
label = "Edit Nickname",
onClick = onEditNickname
)
// Copy URL action
ActionButton(
icon = Icons.Filled.ContentCopy,
label = "Copy Mint URL",
onClick = onCopyUrl
)
// Delete mint action
ActionButton(
icon = Icons.Filled.Delete,
label = "Delete Mint",
onClick = onDeleteMint,
isDestructive = true
)
}
}
}
@Composable
private fun ActionButton(
icon: ImageVector,
label: String,
onClick: () -> Unit,
isDestructive: Boolean = false,
modifier: Modifier = Modifier
) {
val iconColor = if (isDestructive) Color(0xFFFF453A) else Color.Gray
val textColor = if (isDestructive) Color(0xFFFF453A) else Color.White
Row(
modifier = modifier
.fillMaxWidth()
.clickable { onClick() }
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = iconColor,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = label,
color = textColor,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
}
}
@Composable
private fun SectionDivider(
title: String,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(Color(0xFF333333))
)
Text(
text = title,
color = Color.White,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace,
modifier = Modifier.padding(horizontal = 10.dp)
)
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(Color(0xFF333333))
)
}
}
@@ -0,0 +1,301 @@
package com.bitchat.android.wallet.ui.mintinfo
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.bitchat.android.wallet.data.Mint
/**
* Dialog for editing mint nickname
*/
@Composable
fun EditMintNicknameDialog(
currentNickname: String,
onSave: (String) -> Unit,
onDismiss: () -> Unit
) {
var nickname by remember { mutableStateOf(currentNickname) }
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Column(
modifier = Modifier.padding(24.dp)
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Edit Mint Nickname",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Close",
tint = Color.Gray
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Nickname input
OutlinedTextField(
value = nickname,
onValueChange = { nickname = it },
label = {
Text(
text = "Nickname",
fontFamily = FontFamily.Monospace
)
},
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFF00C851),
focusedLabelColor = Color(0xFF00C851),
unfocusedTextColor = Color.White,
focusedTextColor = Color.White
),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
// Action buttons
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Cancel button
OutlinedButton(
onClick = onDismiss,
modifier = Modifier.weight(1f),
border = androidx.compose.foundation.BorderStroke(
1.dp,
Color.Gray
),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = "Cancel",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
}
// Save button
Button(
onClick = { onSave(nickname) },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851)
),
shape = RoundedCornerShape(8.dp),
enabled = nickname.isNotEmpty()
) {
Text(
text = "Save",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
}
}
}
}
}
}
/**
* Dialog for confirming mint deletion
*/
@Composable
fun DeleteMintDialog(
mint: Mint,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Column(
modifier = Modifier.padding(24.dp)
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Delete Mint",
color = Color(0xFFFF453A),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Close",
tint = Color.Gray
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Warning message
Text(
text = "Are you sure you want to delete this mint from your wallet?",
color = Color.White,
fontSize = 16.sp,
fontFamily = FontFamily.Monospace,
lineHeight = 24.sp
)
Spacer(modifier = Modifier.height(16.dp))
// Mint details
Card(
colors = CardDefaults.cardColors(
containerColor = Color(0x10FFFFFF)
),
shape = RoundedCornerShape(8.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Nickname
MintDetailRow(
label = "Nickname:",
value = mint.nickname
)
// URL
MintDetailRow(
label = "URL:",
value = mint.url
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Warning note
Text(
text = "⚠️ This action cannot be undone. Any tokens from this mint will need to be re-added manually.",
color = Color(0xFFF18408),
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
lineHeight = 20.sp
)
Spacer(modifier = Modifier.height(24.dp))
// Action buttons
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Cancel button
OutlinedButton(
onClick = onDismiss,
modifier = Modifier.weight(1f),
border = androidx.compose.foundation.BorderStroke(
1.dp,
Color.Gray
),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = "Cancel",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
}
// Delete button
Button(
onClick = onConfirm,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFFFF453A)
),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = "Delete",
color = Color.White,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
}
}
}
}
}
}
@Composable
private fun MintDetailRow(
label: String,
value: String,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
Text(
text = label,
color = Color.Gray,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
modifier = Modifier.width(80.dp)
)
Text(
text = value,
color = Color.White,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
modifier = Modifier.weight(1f)
)
}
}
@@ -0,0 +1,267 @@
package com.bitchat.android.wallet.ui.mintinfo
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bitchat.android.wallet.data.Mint
import com.bitchat.android.wallet.viewmodel.WalletViewModel
/**
* Detailed mint information screen with terminal-like design
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MintDetailsScreen(
mintUrl: String,
walletViewModel: WalletViewModel,
onNavigateBack: () -> Unit,
modifier: Modifier = Modifier
) {
val mints by walletViewModel.mints.observeAsState(emptyList())
val isLoading by walletViewModel.isLoading.observeAsState(false)
val errorMessage by walletViewModel.errorMessage.observeAsState()
// Find the mint by URL
val mint = mints.find { it.url == mintUrl }
// Show error if mint not found
if (mint == null) {
MintNotFoundScreen(onNavigateBack = onNavigateBack)
return
}
// State for MOTD dismissal
var isMotdDismissed by remember { mutableStateOf(false) }
// State for dialogs
var showEditDialog by remember { mutableStateOf(false) }
var showDeleteDialog by remember { mutableStateOf(false) }
Column(
modifier = modifier
.fillMaxSize()
.background(Color.Black)
) {
// Header with back button
TopAppBar(
title = {
Text(
text = "Mint Details",
color = Color(0xFF00C851),
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
},
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color(0xFF00C851)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Black
)
)
// Scrollable content
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Mint Header Section
MintHeaderSection(mint = mint)
// MOTD Section
if (mint.info?.motd?.isNotEmpty() == true) {
MintMotdComponent(
message = mint.info.motd,
isDismissed = isMotdDismissed,
onDismiss = { isMotdDismissed = true }
)
}
// Description Section
MintDescriptionSection(mint = mint)
// Contact Section
if (mint.info?.contact?.isNotEmpty() == true) {
MintContactSection(contacts = mint.info.contact)
}
// Details Section
MintDetailsSection(mint = mint)
// Actions Section
MintActionButtons(
mint = mint,
onEditNickname = { showEditDialog = true },
onCopyUrl = { walletViewModel.copyToClipboard(mint.url) },
onDeleteMint = { showDeleteDialog = true }
)
// Error display
errorMessage?.let { error ->
ErrorCard(
message = error,
onDismiss = { walletViewModel.clearError() }
)
}
// Bottom spacing
Spacer(modifier = Modifier.height(32.dp))
}
}
// Edit nickname dialog
if (showEditDialog) {
EditMintNicknameDialog(
currentNickname = mint.nickname,
onSave = { newNickname ->
walletViewModel.updateMintNickname(mint.url, newNickname)
showEditDialog = false
},
onDismiss = { showEditDialog = false }
)
}
// Delete mint dialog
if (showDeleteDialog) {
DeleteMintDialog(
mint = mint,
onConfirm = {
walletViewModel.deleteMint(mint.url)
showDeleteDialog = false
onNavigateBack()
},
onDismiss = { showDeleteDialog = false }
)
}
// Loading overlay
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
color = Color(0xFF00C851)
)
}
}
}
@Composable
private fun MintNotFoundScreen(
onNavigateBack: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Filled.Warning,
contentDescription = "Error",
tint = Color.Red,
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Mint Not Found",
color = Color.White,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Text(
text = "The requested mint could not be found",
color = Color.Gray,
fontSize = 16.sp,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = onNavigateBack,
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851)
)
) {
Text(
text = "Go Back",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
}
}
}
@Composable
private fun ErrorCard(
message: String,
onDismiss: () -> Unit
) {
Card(
colors = CardDefaults.cardColors(
containerColor = Color(0x30FF0000)
)
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Warning,
contentDescription = "Error",
tint = Color.Red,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = message,
color = Color.Red,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
modifier = Modifier.weight(1f)
)
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Close",
tint = Color.Red,
modifier = Modifier.size(16.dp)
)
}
}
}
}
@@ -0,0 +1,436 @@
package com.bitchat.android.wallet.ui.mintinfo
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.bitchat.android.wallet.data.ContactInfo
import com.bitchat.android.wallet.data.Mint
import java.text.SimpleDateFormat
import java.util.*
/**
* Header section showing mint icon and name
*/
@Composable
fun MintHeaderSection(
mint: Mint,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1A1A1A).copy(alpha = 0.05f) // Very subtle background
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Mint icon/avatar
Card(
modifier = Modifier.size(56.dp),
shape = RoundedCornerShape(28.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF2A2A2A)
)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
// For now, we'll use the AccountBalance icon as default
// In the future, could load from mint.info?.iconUrl
Icon(
imageVector = Icons.Filled.AccountBalance,
contentDescription = "Mint Icon",
tint = Color(0xFF00C851),
modifier = Modifier.size(32.dp)
)
}
}
Spacer(modifier = Modifier.height(12.dp))
// Mint name from info or fallback to nickname
Text(
text = mint.info?.name?.takeIf { it.isNotEmpty() } ?: mint.nickname,
color = Color.White,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
// Show nickname separately if different from name
if (mint.info?.name?.isNotEmpty() == true && mint.info.name != mint.nickname) {
Text(
text = "\"${mint.nickname}\"",
color = Color.Gray,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace
)
}
}
}
}
/**
* Description section showing mint description and long description
*/
@Composable
fun MintDescriptionSection(
mint: Mint,
modifier: Modifier = Modifier
) {
val info = mint.info
if (info?.description.isNullOrEmpty() && info?.descriptionLong.isNullOrEmpty()) {
return
}
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Short description
info?.description?.takeIf { it.isNotEmpty() }?.let { description ->
Text(
text = description,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
lineHeight = 24.sp,
fontFamily = FontFamily.Monospace
)
}
// Long description
info?.descriptionLong?.takeIf { it.isNotEmpty() }?.let { longDescription ->
Text(
text = longDescription,
color = Color.Gray,
fontSize = 14.sp,
lineHeight = 20.sp,
fontFamily = FontFamily.Monospace
)
}
}
}
/**
* Contact section showing mint contact information
*/
@Composable
fun MintContactSection(
contacts: List<ContactInfo>,
modifier: Modifier = Modifier
) {
if (contacts.isEmpty()) return
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Section divider and title
SectionDivider(title = "CONTACT")
// Contact items
contacts.forEach { contact ->
ContactItem(contact = contact)
}
}
}
/**
* Details section showing mint technical information
*/
@Composable
fun MintDetailsSection(
mint: Mint,
modifier: Modifier = Modifier
) {
var showAllNuts by remember { mutableStateOf(false) }
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Section divider and title
SectionDivider(title = "DETAILS")
// URL
DetailItem(
icon = Icons.Filled.Link,
label = "URL",
value = mint.url,
onClick = { /* Copy URL handled by parent */ }
)
// NUTs (Cashu specifications)
mint.info?.nuts?.let { nuts ->
val supportedNuts = nuts.filter { (_, nutInfo) ->
when (nutInfo) {
is Map<*, *> -> nutInfo["supported"] == true || nutInfo["disabled"] != true
else -> true
}
}
if (supportedNuts.isNotEmpty()) {
DetailItem(
icon = Icons.Filled.Extension,
label = "NUTs",
value = if (showAllNuts) "Hide" else "Show all",
onClick = { showAllNuts = !showAllNuts }
)
if (showAllNuts) {
NutsExpandedSection(nuts = supportedNuts)
}
}
}
// Version
mint.info?.version?.takeIf { it.isNotEmpty() }?.let { version ->
DetailItem(
icon = Icons.Filled.Info,
label = "Version",
value = version
)
}
// Date added
DetailItem(
icon = Icons.Filled.CalendarToday,
label = "Added",
value = formatDate(mint.dateAdded)
)
}
}
@Composable
private fun ContactItem(
contact: ContactInfo,
modifier: Modifier = Modifier
) {
val clipboardManager = LocalClipboardManager.current
Row(
modifier = modifier
.fillMaxWidth()
.clickable {
clipboardManager.setText(AnnotatedString(contact.info))
}
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Contact method icon
val icon = when (contact.method.lowercase()) {
"email" -> Icons.Filled.Email
"twitter", "x" -> Icons.Filled.Share
"nostr" -> Icons.Filled.Public
else -> Icons.Filled.ContactMail
}
Icon(
imageVector = icon,
contentDescription = contact.method,
tint = Color.Gray,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = contact.info,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace,
modifier = Modifier.weight(1f),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
Icon(
imageVector = Icons.Filled.ContentCopy,
contentDescription = "Copy",
tint = Color.Gray,
modifier = Modifier.size(20.dp)
)
}
}
@Composable
private fun DetailItem(
icon: ImageVector,
label: String,
value: String,
onClick: (() -> Unit)? = null,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.then(
if (onClick != null) Modifier.clickable { onClick() }
else Modifier
)
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = label,
tint = Color.Gray,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = label,
color = Color.Gray,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = value,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace,
textAlign = androidx.compose.ui.text.style.TextAlign.Right,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(0.6f)
)
}
}
@Composable
private fun NutsExpandedSection(
nuts: Map<String, Any>,
modifier: Modifier = Modifier
) {
val nutNames = mapOf(
"7" to "Token state check",
"8" to "Overpaid Lightning fees",
"9" to "Signature restore",
"10" to "Spending conditions",
"11" to "Pay-To-Pubkey (P2PK)",
"12" to "DLEQ proofs",
"13" to "Deterministic secrets",
"14" to "Hashed Timelock Contracts",
"15" to "Partial multi-path payments",
"16" to "Animated QR codes",
"17" to "WebSocket subscriptions",
"18" to "Payment requests",
"19" to "Cached Responses",
"20" to "Signature on Mint Quote",
"21" to "Clear authentication",
"22" to "Blind authentication"
)
Column(
modifier = modifier
.fillMaxWidth()
.padding(start = 32.dp), // Indent under the main detail item
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
nuts.filter { (nutNumber, _) ->
nutNumber.toIntOrNull()?.let { it >= 7 } == true // Only show NUT-7 and above
}.forEach { (nutNumber, _) ->
val nutName = nutNames[nutNumber] ?: "Unknown NUT"
Card(
shape = RoundedCornerShape(4.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF2A2A2A)
)
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "$nutNumber:",
color = Color.Gray,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = nutName,
color = Color.White,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
}
}
}
}
}
@Composable
private fun SectionDivider(
title: String,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(Color(0xFF333333))
)
Text(
text = title,
color = Color.White,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace,
modifier = Modifier.padding(horizontal = 10.dp)
)
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(Color(0xFF333333))
)
}
}
// Utility function
private fun formatDate(date: Date): String {
return SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date)
}
@@ -0,0 +1,146 @@
package com.bitchat.android.wallet.ui.mintinfo
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* Component for displaying mint MOTD (Message of the Day) with dismiss functionality
*/
@Composable
fun MintMotdComponent(
message: String,
isDismissed: Boolean,
onDismiss: () -> Unit,
modifier: Modifier = Modifier
) {
if (message.isEmpty()) return
if (!isDismissed) {
// Active MOTD card
Card(
modifier = modifier
.fillMaxWidth()
.clickable(enabled = false) { }, // Prevent accidental clicks
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(
containerColor = Color.Black
),
border = BorderStroke(1.dp, Color(0xFFF18408)) // Orange border like in Vue version
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.Top
) {
// Info icon
Icon(
imageVector = Icons.Filled.Info,
contentDescription = "MOTD",
tint = Color(0xFFF18408),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
// Content
Column(
modifier = Modifier.weight(1f)
) {
// Header with title and close button
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Message of the Day",
color = Color(0xFFF18408),
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
IconButton(
onClick = onDismiss,
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Dismiss",
tint = Color(0xFFF18408),
modifier = Modifier.size(18.dp)
)
}
}
Spacer(modifier = Modifier.height(2.dp))
// MOTD message
Text(
text = message,
color = Color(0xFFF18408),
fontSize = 14.sp,
lineHeight = 16.sp,
fontFamily = FontFamily.Monospace
)
}
}
}
} else {
// Dismissed MOTD - minimal display
Card(
modifier = modifier
.fillMaxWidth()
.clickable { /* Could expand back if needed */ },
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF1A1A1A)
)
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Info,
contentDescription = "MOTD",
tint = Color.Gray,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(
text = "Message of the Day",
color = Color.Gray,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
Text(
text = message,
color = Color.Gray,
fontSize = 14.sp,
lineHeight = 16.sp,
fontFamily = FontFamily.Monospace,
maxLines = 2 // Truncate in dismissed state
)
}
}
}
}
}
@@ -271,6 +271,43 @@ class MintManager(
} }
} }
/**
* Delete a mint from the repository
*/
fun deleteMint(mintUrl: String) {
coroutineScope.launch {
try {
val currentMints = _mints.value ?: emptyList()
val updatedMints = currentMints.filter { it.url != mintUrl }
// Save updated mints list (remove the mint)
repository.clearAllData()
updatedMints.forEach { mint ->
repository.saveMint(mint).onFailure { error ->
Log.e(TAG, "Failed to save mint during deletion: ${mint.url}", error)
}
}
// If the deleted mint was active, clear active mint
if (_activeMint.value == mintUrl) {
repository.setActiveMint("")
_activeMint.value = null
}
// Reload mints
repository.getMints().onSuccess { mintList ->
_mints.value = mintList
}
Log.d(TAG, "Successfully deleted mint: $mintUrl")
} catch (e: Exception) {
Log.e(TAG, "Failed to delete mint: $mintUrl", e)
_errorMessage.value = "Failed to delete mint: ${e.message}"
}
}
}
/** /**
* Clear error message * Clear error message
*/ */
@@ -24,6 +24,12 @@ class UIStateManager {
private val _showAddMintDialog = MutableLiveData<Boolean>(false) private val _showAddMintDialog = MutableLiveData<Boolean>(false)
val showAddMintDialog: androidx.lifecycle.LiveData<Boolean> = _showAddMintDialog val showAddMintDialog: androidx.lifecycle.LiveData<Boolean> = _showAddMintDialog
private val _showMintDetails = MutableLiveData<Boolean>(false)
val showMintDetails: androidx.lifecycle.LiveData<Boolean> = _showMintDetails
private val _selectedMintUrl = MutableLiveData<String?>(null)
val selectedMintUrl: androidx.lifecycle.LiveData<String?> = _selectedMintUrl
// Success animation state // Success animation state
private val _showSuccessAnimation = MutableLiveData<Boolean>(false) private val _showSuccessAnimation = MutableLiveData<Boolean>(false)
val showSuccessAnimation: androidx.lifecycle.LiveData<Boolean> = _showSuccessAnimation val showSuccessAnimation: androidx.lifecycle.LiveData<Boolean> = _showSuccessAnimation
@@ -84,6 +90,16 @@ class UIStateManager {
_showAddMintDialog.value = false _showAddMintDialog.value = false
} }
fun showMintDetails(mintUrl: String) {
_selectedMintUrl.value = mintUrl
_showMintDetails.value = true
}
fun hideMintDetails() {
_showMintDetails.value = false
_selectedMintUrl.value = null
}
// Animation Management // Animation Management
fun showSuccessAnimation(animationData: WalletViewModel.SuccessAnimationData) { fun showSuccessAnimation(animationData: WalletViewModel.SuccessAnimationData) {
@@ -58,6 +58,8 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val sendType: LiveData<SendType> = uiStateManager.sendType val sendType: LiveData<SendType> = uiStateManager.sendType
val receiveType: LiveData<ReceiveType> = uiStateManager.receiveType val receiveType: LiveData<ReceiveType> = uiStateManager.receiveType
val showAddMintDialog: LiveData<Boolean> = uiStateManager.showAddMintDialog val showAddMintDialog: LiveData<Boolean> = uiStateManager.showAddMintDialog
val showMintDetails: LiveData<Boolean> = uiStateManager.showMintDetails
val selectedMintUrl: LiveData<String?> = uiStateManager.selectedMintUrl
val showSuccessAnimation: LiveData<Boolean> = uiStateManager.showSuccessAnimation val showSuccessAnimation: LiveData<Boolean> = uiStateManager.showSuccessAnimation
val successAnimationData: LiveData<SuccessAnimationData?> = uiStateManager.successAnimationData val successAnimationData: LiveData<SuccessAnimationData?> = uiStateManager.successAnimationData
val showFailureAnimation: LiveData<Boolean> = uiStateManager.showFailureAnimation val showFailureAnimation: LiveData<Boolean> = uiStateManager.showFailureAnimation
@@ -182,6 +184,20 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
fun clearError() = uiStateManager.clearError() fun clearError() = uiStateManager.clearError()
// Mint details navigation
fun showMintDetails(mintUrl: String) = uiStateManager.showMintDetails(mintUrl)
fun hideMintDetails() = uiStateManager.hideMintDetails()
// Copy to clipboard functionality
fun copyToClipboard(text: String) {
// This will be handled by the UI components directly using LocalClipboardManager
// We can add a success message here if needed
}
// Delete mint functionality
fun deleteMint(mintUrl: String) = mintManager.deleteMint(mintUrl)
// Token input management - Delegate to TokenManager // Token input management - Delegate to TokenManager
fun setTokenInput(token: String) = tokenManager.setTokenInput(token) fun setTokenInput(token: String) = tokenManager.setTokenInput(token)