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,132 +14,156 @@ 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(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) { ) {
Card( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.padding(16.dp), .padding(16.dp)
shape = RoundedCornerShape(16.dp), .verticalScroll(rememberScrollState())
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) { ) {
Column( // Header with back button
Row(
modifier = Modifier modifier = Modifier
.padding(24.dp) .fillMaxWidth()
.verticalScroll(rememberScrollState()) .padding(bottom = 24.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
// Header IconButton(
Row( onClick = onNavigateBack,
modifier = Modifier.fillMaxWidth(), modifier = Modifier
horizontalArrangement = Arrangement.SpaceBetween, .size(48.dp)
verticalAlignment = Alignment.CenterVertically .clip(RoundedCornerShape(12.dp))
.background(Color(0xFF1A1A1A))
) { ) {
Text( Icon(
text = "Receive", imageVector = Icons.Filled.ArrowBack,
color = Color.White, contentDescription = "Back",
fontSize = 20.sp, tint = Color.White,
fontWeight = FontWeight.Bold, modifier = Modifier.size(24.dp)
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)
) )
} }
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 // Spacer to center the title
when (receiveType) { Spacer(modifier = Modifier.width(48.dp))
WalletViewModel.ReceiveType.CASHU -> { }
CashuReceiveContent(
viewModel = viewModel, // Receive type selector (only show when not displaying QR code or token info)
decodedToken = decodedToken, if (showTypeSelector) {
isLoading = isLoading 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 -> { FilterChip(
LightningReceiveContent( onClick = { viewModel.setReceiveType(WalletViewModel.ReceiveType.LIGHTNING) },
viewModel = viewModel, label = {
currentMintQuote = currentMintQuote, Text(
isLoading = isLoading 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) { if (decodedToken != null) {
// Show decoded token info and receive button // Show decoded token info and receive button
Column { Column(
Text( modifier = Modifier.fillMaxWidth(),
text = "Cashu Token Details", horizontalAlignment = Alignment.CenterHorizontally
color = Color(0xFF00C851), ) {
fontSize = 16.sp, // Token info card
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
Spacer(modifier = Modifier.height(16.dp))
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
shape = RoundedCornerShape(8.dp), .fillMaxWidth()
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A)) .padding(bottom = 32.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
) { ) {
Column(modifier = Modifier.padding(16.dp)) { Column(
Row( modifier = Modifier
modifier = Modifier.fillMaxWidth(), .fillMaxWidth()
horizontalArrangement = Arrangement.SpaceBetween .padding(24.dp),
) { horizontalAlignment = Alignment.CenterHorizontally
Text( ) {
text = "Amount:", Icon(
color = Color.Gray, imageVector = Icons.Filled.Toll,
fontFamily = FontFamily.Monospace contentDescription = "Token",
) tint = Color(0xFF00C851),
Text( modifier = Modifier.size(48.dp)
text = formatSats(decodedToken.amount.toLong()), )
color = Color.White,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(16.dp))
Row( Text(
modifier = Modifier.fillMaxWidth(), text = "CASHU TOKEN",
horizontalArrangement = Arrangement.SpaceBetween color = Color(0xFF00C851),
) { fontSize = 16.sp,
Text( fontWeight = FontWeight.Bold,
text = "Mint:", fontFamily = FontFamily.Monospace,
color = Color.Gray, letterSpacing = 1.sp
fontFamily = FontFamily.Monospace )
)
Text( Spacer(modifier = Modifier.height(24.dp))
text = decodedToken.mint.take(20) + if (decodedToken.mint.length > 20) "..." else "",
color = Color.White, // Amount
fontFamily = FontFamily.Monospace 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()) { if (!decodedToken.memo.isNullOrEmpty()) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Row( Text(
modifier = Modifier.fillMaxWidth(), text = "Memo: ${decodedToken.memo}",
horizontalArrangement = Arrangement.SpaceBetween color = Color.Gray,
) { fontSize = 14.sp,
Text( fontFamily = FontFamily.Monospace,
text = "Memo:", textAlign = TextAlign.Center
color = Color.Gray, )
fontFamily = FontFamily.Monospace
)
Text(
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 {
Icon( Row(
imageVector = Icons.Filled.Download, verticalAlignment = Alignment.CenterVertically
contentDescription = "Receive", ) {
tint = Color.Black Icon(
) imageVector = Icons.Filled.Download,
Spacer(modifier = Modifier.width(8.dp)) contentDescription = "Receive",
Text( tint = Color.Black,
text = "Receive Token", modifier = Modifier.size(24.dp)
color = Color.Black, )
fontWeight = FontWeight.Bold, Spacer(modifier = Modifier.width(12.dp))
fontFamily = FontFamily.Monospace Text(
) text = "RECEIVE TOKEN",
color = Color.Black,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
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,28 +359,37 @@ 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)
) { ) {
Icon( Row(
imageVector = Icons.Filled.ContentPaste, verticalAlignment = Alignment.CenterVertically
contentDescription = "Paste", ) {
tint = Color(0xFF00C851) Icon(
) imageVector = Icons.Filled.ContentPaste,
Spacer(modifier = Modifier.width(8.dp)) contentDescription = "Paste",
Text( tint = Color(0xFF00C851),
text = "Paste from clipboard", modifier = Modifier.size(24.dp)
color = Color(0xFF00C851), )
fontWeight = FontWeight.Bold, Spacer(modifier = Modifier.width(12.dp))
fontFamily = FontFamily.Monospace 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) { if (currentMintQuote != null) {
// Show Lightning invoice QR code and details // Show Lightning invoice QR code and details
Column { Column(
Text( modifier = Modifier.fillMaxWidth(),
text = "Lightning Invoice", horizontalAlignment = Alignment.CenterHorizontally
color = Color(0xFF00C851), ) {
fontSize = 16.sp, // Invoice header
fontWeight = FontWeight.Bold, Card(
fontFamily = FontFamily.Monospace modifier = Modifier
) .fillMaxWidth()
.padding(bottom = 24.dp),
Spacer(modifier = Modifier.height(16.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 // 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)
) { ) {
Icon( Row(
imageVector = Icons.Filled.ContentCopy, verticalAlignment = Alignment.CenterVertically
contentDescription = "Copy", ) {
tint = Color(0xFF00C851) Icon(
) imageVector = Icons.Filled.ContentCopy,
Spacer(modifier = Modifier.width(8.dp)) contentDescription = "Copy",
Text( tint = Color(0xFFFFB000),
text = "Copy Invoice", modifier = Modifier.size(24.dp)
color = Color(0xFF00C851), )
fontWeight = FontWeight.Bold, Spacer(modifier = Modifier.width(12.dp))
fontFamily = FontFamily.Monospace Text(
) text = "COPY INVOICE",
color = Color(0xFFFFB000),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
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),
unfocusedTextColor = Color.White, unfocusedBorderColor = Color(0xFF2A2A2A),
unfocusedLabelColor = Color.Gray,
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,30 +601,40 @@ 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 {
Icon( Row(
imageVector = Icons.Filled.FlashOn, verticalAlignment = Alignment.CenterVertically
contentDescription = "Create Invoice", ) {
tint = Color.Black Icon(
) imageVector = Icons.Filled.FlashOn,
Spacer(modifier = Modifier.width(8.dp)) contentDescription = "Create Invoice",
Text( tint = Color.Black,
text = "Create Invoice", modifier = Modifier.size(24.dp)
color = Color.Black, )
fontWeight = FontWeight.Bold, Spacer(modifier = Modifier.width(12.dp))
fontFamily = FontFamily.Monospace 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 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)
Column(modifier = modifier.fillMaxSize()) { if (showReceiveView) {
// Content // Full-screen ReceiveView
when (selectedTab) { ReceiveView(
0 -> WalletOverview(viewModel = walletViewModel, modifier = Modifier.weight(1f)) viewModel = walletViewModel,
1 -> TransactionHistory( onNavigateBack = {
transactions = walletViewModel.getAllTransactions().observeAsState(initial = emptyList()).value, showReceiveView = false
modifier = Modifier.weight(1f) walletViewModel.hideReceiveDialog()
) }
2 -> MintsScreen(viewModel = walletViewModel, modifier = Modifier.weight(1f)) )
3 -> WalletSettings( } else {
viewModel = walletViewModel, Column(modifier = modifier.fillMaxSize()) {
onBackClick = { /* No back action needed in tab navigation */ } // 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 // Dialogs
@@ -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
/** /**