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