better receive view but it freezes

This commit is contained in:
callebtc
2025-07-15 01:13:32 +02:00
parent 8d3b8e0765
commit aa12d8750e
4 changed files with 457 additions and 319 deletions
@@ -27,10 +27,11 @@ fun QRCodeCanvas(
size: Dp = 200.dp
) {
val density = LocalDensity.current
val sizePx = with(density) { size.toPx() }.toInt()
// Use a larger fixed size for better quality
val qrCodeBitMatrix = remember(text) {
try {
val sizePx = with(density) { 512.dp.toPx() }.toInt() // Fixed high resolution
val writer = QRCodeWriter()
writer.encode(text, BarcodeFormat.QR_CODE, sizePx, sizePx)
} catch (e: WriterException) {
@@ -40,20 +41,21 @@ fun QRCodeCanvas(
Canvas(
modifier = modifier
.size(size)
.background(Color.White)
) {
qrCodeBitMatrix?.let { bitMatrix ->
drawQRCode(bitMatrix, sizePx)
// Use the actual canvas size, not a calculated size
val canvasSize = minOf(this.size.width, this.size.height)
drawQRCode(bitMatrix, canvasSize)
}
}
}
private fun DrawScope.drawQRCode(
bitMatrix: com.google.zxing.common.BitMatrix,
sizePx: Int
sizePx: Float
) {
val cellSize = sizePx.toFloat() / bitMatrix.width
val cellSize = sizePx / bitMatrix.width
for (x in 0 until bitMatrix.width) {
for (y in 0 until bitMatrix.height) {
@@ -1,5 +1,6 @@
package com.bitchat.android.wallet.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -13,132 +14,156 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
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.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
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.MintQuote
import com.bitchat.android.wallet.data.CashuToken
import com.bitchat.android.wallet.viewmodel.WalletViewModel
/**
* Receive dialog with options for Cashu tokens or Lightning invoices
* Full-screen receive view with options for Cashu tokens or Lightning invoices
*/
@Composable
fun ReceiveDialog(
fun ReceiveView(
viewModel: WalletViewModel,
onDismiss: () -> Unit
onNavigateBack: () -> Unit
) {
val receiveType by viewModel.receiveType.observeAsState(WalletViewModel.ReceiveType.CASHU)
val decodedToken by viewModel.decodedToken.observeAsState()
val currentMintQuote by viewModel.currentMintQuote.observeAsState()
val isLoading by viewModel.isLoading.observeAsState(false)
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
// Determine if we should show type selector
val showTypeSelector = decodedToken == null && currentMintQuote == null
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Card(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Column(
// Header with back button
Row(
modifier = Modifier
.padding(24.dp)
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(bottom = 24.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
IconButton(
onClick = onNavigateBack,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF1A1A1A))
) {
Text(
text = "Receive",
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))
// Receive type selector
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilterChip(
onClick = { viewModel.setReceiveType(WalletViewModel.ReceiveType.CASHU) },
label = {
Text(
text = "Cashu",
fontFamily = FontFamily.Monospace
)
},
selected = receiveType == WalletViewModel.ReceiveType.CASHU,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = Color(0xFF00C851),
selectedLabelColor = Color.Black
),
modifier = Modifier.weight(1f)
)
FilterChip(
onClick = { viewModel.setReceiveType(WalletViewModel.ReceiveType.LIGHTNING) },
label = {
Text(
text = "Lightning",
fontFamily = FontFamily.Monospace
)
},
selected = receiveType == WalletViewModel.ReceiveType.LIGHTNING,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = Color(0xFF00C851),
selectedLabelColor = Color.Black
),
modifier = Modifier.weight(1f)
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "RECEIVE",
color = Color.White,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 2.sp
)
// Content based on receive type
when (receiveType) {
WalletViewModel.ReceiveType.CASHU -> {
CashuReceiveContent(
viewModel = viewModel,
decodedToken = decodedToken,
isLoading = isLoading
// Spacer to center the title
Spacer(modifier = Modifier.width(48.dp))
}
// Receive type selector (only show when not displaying QR code or token info)
if (showTypeSelector) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilterChip(
onClick = { viewModel.setReceiveType(WalletViewModel.ReceiveType.CASHU) },
label = {
Text(
text = "Cashu Token",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium
)
},
selected = receiveType == WalletViewModel.ReceiveType.CASHU,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = Color(0xFF00C851),
selectedLabelColor = Color.Black,
containerColor = Color(0xFF2A2A2A),
labelColor = Color.White
),
modifier = Modifier.weight(1f)
)
}
WalletViewModel.ReceiveType.LIGHTNING -> {
LightningReceiveContent(
viewModel = viewModel,
currentMintQuote = currentMintQuote,
isLoading = isLoading
FilterChip(
onClick = { viewModel.setReceiveType(WalletViewModel.ReceiveType.LIGHTNING) },
label = {
Text(
text = "Lightning Invoice",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium
)
},
selected = receiveType == WalletViewModel.ReceiveType.LIGHTNING,
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = Color(0xFF00C851),
selectedLabelColor = Color.Black,
containerColor = Color(0xFF2A2A2A),
labelColor = Color.White
),
modifier = Modifier.weight(1f)
)
}
}
}
// Content based on receive type
when (receiveType) {
WalletViewModel.ReceiveType.CASHU -> {
CashuReceiveContent(
viewModel = viewModel,
decodedToken = decodedToken,
isLoading = isLoading
)
}
WalletViewModel.ReceiveType.LIGHTNING -> {
LightningReceiveContent(
viewModel = viewModel,
currentMintQuote = currentMintQuote,
isLoading = isLoading
)
}
}
}
}
}
@@ -154,116 +179,126 @@ private fun CashuReceiveContent(
if (decodedToken != null) {
// Show decoded token info and receive button
Column {
Text(
text = "Cashu Token Details",
color = Color(0xFF00C851),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Token info card
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Amount:",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
Text(
text = formatSats(decodedToken.amount.toLong()),
color = Color.White,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Filled.Toll,
contentDescription = "Token",
tint = Color(0xFF00C851),
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Mint:",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
Text(
text = decodedToken.mint.take(20) + if (decodedToken.mint.length > 20) "..." else "",
color = Color.White,
fontFamily = FontFamily.Monospace
)
}
Text(
text = "CASHU TOKEN",
color = Color(0xFF00C851),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
Spacer(modifier = Modifier.height(24.dp))
// Amount
Text(
text = formatSats(decodedToken.amount.toLong()),
color = Color.White,
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
// Mint info
Text(
text = "From: ${decodedToken.mint.take(30)}${if (decodedToken.mint.length > 30) "..." else ""}",
color = Color.Gray,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
textAlign = TextAlign.Center
)
if (!decodedToken.memo.isNullOrEmpty()) {
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Memo:",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
Text(
text = decodedToken.memo,
color = Color.White,
fontFamily = FontFamily.Monospace
)
}
Text(
text = "Memo: ${decodedToken.memo}",
color = Color.Gray,
fontSize = 14.sp,
fontFamily = FontFamily.Monospace,
textAlign = TextAlign.Center
)
}
}
}
Spacer(modifier = Modifier.height(24.dp))
// Receive button
Button(
onClick = {
viewModel.receiveCashuToken(decodedToken.token)
},
enabled = !isLoading,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851)
containerColor = Color(0xFF00C851),
disabledContainerColor = Color(0xFF2A2A2A)
),
shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(16.dp)
) {
if (isLoading) {
CircularProgressIndicator(
color = Color.Black,
modifier = Modifier.size(16.dp)
modifier = Modifier.size(24.dp)
)
} else {
Icon(
imageVector = Icons.Filled.Download,
contentDescription = "Receive",
tint = Color.Black
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Receive Token",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Download,
contentDescription = "Receive",
tint = Color.Black,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "RECEIVE TOKEN",
color = Color.Black,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
}
}
}
} else {
// Show token input
Column {
Column(
modifier = Modifier.fillMaxWidth()
) {
// Token input field
OutlinedTextField(
value = token,
onValueChange = {
@@ -288,11 +323,17 @@ private fun CashuReceiveContent(
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFF00C851),
focusedLabelColor = Color(0xFF00C851),
unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White,
focusedTextColor = Color.White
),
modifier = Modifier.fillMaxWidth(),
maxLines = 3,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
minLines = 3,
maxLines = 4,
shape = RoundedCornerShape(16.dp),
trailingIcon = {
IconButton(
onClick = {
@@ -300,7 +341,7 @@ private fun CashuReceiveContent(
}
) {
Icon(
imageVector = Icons.Filled.CameraAlt,
imageVector = Icons.Filled.QrCode,
contentDescription = "Scan QR",
tint = Color(0xFF00C851)
)
@@ -308,8 +349,7 @@ private fun CashuReceiveContent(
}
)
Spacer(modifier = Modifier.height(16.dp))
// Paste button
Button(
onClick = {
clipboardManager.getText()?.text?.let { clipText ->
@@ -319,28 +359,37 @@ private fun CashuReceiveContent(
}
}
},
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
border = androidx.compose.foundation.BorderStroke(
1.dp,
2.dp,
Color(0xFF00C851)
),
shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(16.dp)
) {
Icon(
imageVector = Icons.Filled.ContentPaste,
contentDescription = "Paste",
tint = Color(0xFF00C851)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Paste from clipboard",
color = Color(0xFF00C851),
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.ContentPaste,
contentDescription = "Paste",
tint = Color(0xFF00C851),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "PASTE FROM CLIPBOARD",
color = Color(0xFF00C851),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
}
}
}
@@ -358,29 +407,91 @@ private fun LightningReceiveContent(
if (currentMintQuote != null) {
// Show Lightning invoice QR code and details
Column {
Text(
text = "Lightning Invoice",
color = Color(0xFF00C851),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Invoice header
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Filled.FlashOn,
contentDescription = "Lightning",
tint = Color(0xFFFFB000),
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "LIGHTNING INVOICE",
color = Color(0xFFFFB000),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
Spacer(modifier = Modifier.height(24.dp))
// Amount
Text(
text = formatSats(currentMintQuote.amount.toLong()),
color = Color.White,
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
// Status
Row(
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(12.dp)
.clip(RoundedCornerShape(6.dp))
.background(if (currentMintQuote.paid) Color(0xFF00C851) else Color(0xFFFFB000))
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = if (currentMintQuote.paid) "PAID" else "WAITING FOR PAYMENT",
color = if (currentMintQuote.paid) Color(0xFF00C851) else Color(0xFFFFB000),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
}
}
// QR Code
Card(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f),
shape = RoundedCornerShape(8.dp),
.aspectRatio(1f)
.padding(bottom = 24.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
QRCodeCanvas(
@@ -390,85 +501,50 @@ private fun LightningReceiveContent(
}
}
Spacer(modifier = Modifier.height(16.dp))
// Invoice details
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Amount:",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
Text(
text = formatSats(currentMintQuote.amount.toLong()),
color = Color.White,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Status:",
color = Color.Gray,
fontFamily = FontFamily.Monospace
)
Text(
text = if (currentMintQuote.paid) "Paid" else "Waiting...",
color = if (currentMintQuote.paid) Color(0xFF00C851) else Color.Yellow,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Copy invoice button
Button(
onClick = {
clipboardManager.setText(AnnotatedString(currentMintQuote.request))
},
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
border = androidx.compose.foundation.BorderStroke(
1.dp,
Color(0xFF00C851)
2.dp,
Color(0xFFFFB000)
),
shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(16.dp)
) {
Icon(
imageVector = Icons.Filled.ContentCopy,
contentDescription = "Copy",
tint = Color(0xFF00C851)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Copy Invoice",
color = Color(0xFF00C851),
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.ContentCopy,
contentDescription = "Copy",
tint = Color(0xFFFFB000),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "COPY INVOICE",
color = Color(0xFFFFB000),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
}
}
} else {
// Show amount input
Column {
Column(
modifier = Modifier.fillMaxWidth()
) {
// Amount input
OutlinedTextField(
value = amount,
onValueChange = { amount = it },
@@ -482,14 +558,18 @@ private fun LightningReceiveContent(
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFF00C851),
focusedLabelColor = Color(0xFF00C851),
unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White,
focusedTextColor = Color.White
),
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
shape = RoundedCornerShape(16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
// Description input
OutlinedTextField(
value = description,
onValueChange = { description = it },
@@ -502,14 +582,18 @@ private fun LightningReceiveContent(
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color(0xFF00C851),
focusedLabelColor = Color(0xFF00C851),
unfocusedTextColor = Color.White,
unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray,
unfocusedTextColor = Color.White,
focusedTextColor = Color.White
),
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp),
shape = RoundedCornerShape(16.dp)
)
Spacer(modifier = Modifier.height(24.dp))
// Create invoice button
Button(
onClick = {
amount.toLongOrNull()?.let { amountSats ->
@@ -517,30 +601,40 @@ private fun LightningReceiveContent(
}
},
enabled = !isLoading && amount.toLongOrNull()?.let { it > 0 } == true,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF00C851)
containerColor = Color(0xFF00C851),
disabledContainerColor = Color(0xFF2A2A2A)
),
shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(16.dp)
) {
if (isLoading) {
CircularProgressIndicator(
color = Color.Black,
modifier = Modifier.size(16.dp)
modifier = Modifier.size(24.dp)
)
} else {
Icon(
imageVector = Icons.Filled.FlashOn,
contentDescription = "Create Invoice",
tint = Color.Black
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Create Invoice",
color = Color.Black,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.FlashOn,
contentDescription = "Create Invoice",
tint = Color.Black,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "CREATE INVOICE",
color = Color.Black,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace,
letterSpacing = 1.sp
)
}
}
}
}
@@ -25,29 +25,49 @@ fun WalletScreen(
modifier: Modifier = Modifier
) {
var selectedTab by remember { mutableStateOf(0) }
var showReceiveView by remember { mutableStateOf(false) }
val showSendDialog by walletViewModel.showSendDialog.observeAsState(false)
val showReceiveDialog by walletViewModel.showReceiveDialog.observeAsState(false)
Column(modifier = modifier.fillMaxSize()) {
// Content
when (selectedTab) {
0 -> WalletOverview(viewModel = walletViewModel, modifier = Modifier.weight(1f))
1 -> TransactionHistory(
transactions = walletViewModel.getAllTransactions().observeAsState(initial = emptyList()).value,
modifier = Modifier.weight(1f)
)
2 -> MintsScreen(viewModel = walletViewModel, modifier = Modifier.weight(1f))
3 -> WalletSettings(
viewModel = walletViewModel,
onBackClick = { /* No back action needed in tab navigation */ }
if (showReceiveView) {
// Full-screen ReceiveView
ReceiveView(
viewModel = walletViewModel,
onNavigateBack = {
showReceiveView = false
walletViewModel.hideReceiveDialog()
}
)
} else {
Column(modifier = modifier.fillMaxSize()) {
// Content
when (selectedTab) {
0 -> WalletOverview(viewModel = walletViewModel, modifier = Modifier.weight(1f))
1 -> TransactionHistory(
transactions = walletViewModel.getAllTransactions().observeAsState(initial = emptyList()).value,
modifier = Modifier.weight(1f),
onTransactionClick = { transaction ->
// For lightning receive transactions, open the receive view with the quote
if (transaction.type == com.bitchat.android.wallet.data.TransactionType.LIGHTNING_RECEIVE &&
transaction.quote != null) {
walletViewModel.setCurrentMintQuote(transaction.quote!!)
showReceiveView = true
}
}
)
2 -> MintsScreen(viewModel = walletViewModel, modifier = Modifier.weight(1f))
3 -> WalletSettings(
viewModel = walletViewModel,
onBackClick = { /* No back action needed in tab navigation */ }
)
}
// Bottom Navigation
WalletBottomNavigation(
selectedTab = selectedTab,
onTabSelected = { selectedTab = it }
)
}
// Bottom Navigation
WalletBottomNavigation(
selectedTab = selectedTab,
onTabSelected = { selectedTab = it }
)
}
// Dialogs
@@ -59,10 +79,11 @@ fun WalletScreen(
}
if (showReceiveDialog) {
ReceiveDialog(
viewModel = walletViewModel,
onDismiss = { walletViewModel.hideReceiveDialog() }
)
LaunchedEffect(showReceiveDialog) {
if (showReceiveDialog) {
showReceiveView = true
}
}
}
}
@@ -274,6 +274,11 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val updatedQuote = quote.copy(paid = true, state = MintQuoteState.PAID)
repository.saveMintQuote(updatedQuote)
// Update currentMintQuote if it's the same quote
if (_currentMintQuote.value?.id == quote.id) {
_currentMintQuote.value = updatedQuote
}
val transaction = WalletTransaction(
id = UUID.randomUUID().toString(),
type = TransactionType.LIGHTNING_RECEIVE,
@@ -372,6 +377,22 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
_errorMessage.value = null
}
/**
* Set a specific mint quote as current (for reopening from transaction list)
*/
fun setCurrentMintQuote(quoteId: String) {
viewModelScope.launch {
repository.getMintQuotes().onSuccess { quotes ->
val quote = quotes.find { it.id == quoteId }
if (quote != null) {
_currentMintQuote.value = quote
_receiveType.value = ReceiveType.LIGHTNING
showReceiveDialog()
}
}
}
}
// Wallet Operations
/**