mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 03:45:21 +00:00
better receive view but it freezes
This commit is contained in:
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user