mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 19:45:25 +00:00
mint info screen, wip
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user