diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt index 37c9ebfd..a3a78927 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothGattServerManager.kt @@ -171,15 +171,15 @@ class BluetoothGattServerManager( } if (characteristic.uuid == CHARACTERISTIC_UUID) { - Log.i(TAG, "Server: Received packet from ${device.address}, size: ${value.size} bytes") + // Log.i(TAG, "Server: Received packet from ${device.address}, size: ${value.size} bytes") val packet = BitchatPacket.fromBinaryData(value) if (packet != null) { val peerID = String(packet.senderID).replace("\u0000", "") Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID") delegate?.onPacketReceived(packet, peerID, device) } else { - Log.w(TAG, "Server: Failed to parse packet from ${device.address}, size: ${value.size} bytes") - Log.w(TAG, "Server: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}") + // Log.w(TAG, "Server: Failed to parse packet from ${device.address}, size: ${value.size} bytes") + // Log.w(TAG, "Server: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}") } if (responseNeeded) { diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveDialog.kt index abddbb36..cc6f8351 100644 --- a/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveDialog.kt +++ b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveDialog.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -16,16 +14,10 @@ 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 com.bitchat.android.wallet.data.MintQuote -import com.bitchat.android.wallet.data.CashuToken import com.bitchat.android.wallet.viewmodel.WalletViewModel /** @@ -150,14 +142,14 @@ fun ReceiveView( // Content based on receive type when (receiveType) { WalletViewModel.ReceiveType.CASHU -> { - CashuReceiveContent( + ReceiveEcashDialog( viewModel = viewModel, decodedToken = decodedToken, isLoading = isLoading ) } WalletViewModel.ReceiveType.LIGHTNING -> { - LightningReceiveContent( + ReceiveLightningDialog( viewModel = viewModel, currentMintQuote = currentMintQuote, isLoading = isLoading @@ -168,480 +160,4 @@ fun ReceiveView( } } -@Composable -private fun CashuReceiveContent( - viewModel: WalletViewModel, - decodedToken: CashuToken?, - isLoading: Boolean -) { - val token by viewModel.tokenInput.observeAsState("") - val clipboardManager = LocalClipboardManager.current - - if (decodedToken != null) { - // Show decoded token info and receive button - 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 = "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)) - Text( - text = "Memo: ${decodedToken.memo}", - color = Color.Gray, - fontSize = 14.sp, - fontFamily = FontFamily.Monospace, - textAlign = TextAlign.Center - ) - } - } - } - - // Receive button - Button( - onClick = { - viewModel.receiveCashuToken(decodedToken.token) - }, - enabled = !isLoading, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851), - disabledContainerColor = Color(0xFF2A2A2A) - ), - shape = RoundedCornerShape(16.dp) - ) { - if (isLoading) { - CircularProgressIndicator( - color = Color.Black, - modifier = Modifier.size(24.dp) - ) - } else { - 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( - modifier = Modifier.fillMaxWidth() - ) { - // Token input field - OutlinedTextField( - value = token, - onValueChange = { - viewModel.setTokenInput(it) - }, - label = { - Text( - text = "Cashu Token", - fontFamily = FontFamily.Monospace - ) - }, - placeholder = { - Text( - text = "cashu...", - fontFamily = FontFamily.Monospace, - color = Color.Gray - ) - }, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 24.dp), - minLines = 3, - maxLines = 4, - shape = RoundedCornerShape(16.dp), - trailingIcon = { - IconButton( - onClick = { - // TODO: Implement QR code scanning - } - ) { - Icon( - imageVector = Icons.Filled.QrCode, - contentDescription = "Scan QR", - tint = Color(0xFF00C851) - ) - } - } - ) - - // Paste button - Button( - onClick = { - clipboardManager.getText()?.text?.let { clipText -> - if (clipText.startsWith("cashu")) { - viewModel.setTokenInput(clipText) - } - } - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - border = androidx.compose.foundation.BorderStroke( - 2.dp, - Color(0xFF00C851) - ), - shape = RoundedCornerShape(16.dp) - ) { - 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 - ) - } - } - } - } -} -@Composable -private fun LightningReceiveContent( - viewModel: WalletViewModel, - currentMintQuote: MintQuote?, - isLoading: Boolean -) { - var amount by remember { mutableStateOf("") } - var description by remember { mutableStateOf("") } - val clipboardManager = LocalClipboardManager.current - - if (currentMintQuote != null) { - // Show Lightning invoice QR code and details - 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) - .padding(bottom = 24.dp), - shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors(containerColor = Color.White) - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - QRCodeCanvas( - text = currentMintQuote.request, - modifier = Modifier.fillMaxSize() - ) - } - } - - // Copy invoice button - Button( - onClick = { - clipboardManager.setText(AnnotatedString(currentMintQuote.request)) - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - border = androidx.compose.foundation.BorderStroke( - 2.dp, - Color(0xFFFFB000) - ), - shape = RoundedCornerShape(16.dp) - ) { - 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( - modifier = Modifier.fillMaxWidth() - ) { - // Amount input - OutlinedTextField( - value = amount, - onValueChange = { amount = it }, - label = { - Text( - text = "Amount (sats)", - fontFamily = FontFamily.Monospace - ) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp), - shape = RoundedCornerShape(16.dp) - ) - - // Description input - OutlinedTextField( - value = description, - onValueChange = { description = it }, - label = { - Text( - text = "Description (optional)", - fontFamily = FontFamily.Monospace - ) - }, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 32.dp), - shape = RoundedCornerShape(16.dp) - ) - - // Create invoice button - Button( - onClick = { - amount.toLongOrNull()?.let { amountSats -> - viewModel.createMintQuote(amountSats, description.ifEmpty { null }) - } - }, - enabled = !isLoading && amount.toLongOrNull()?.let { it > 0 } == true, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851), - disabledContainerColor = Color(0xFF2A2A2A) - ), - shape = RoundedCornerShape(16.dp) - ) { - if (isLoading) { - CircularProgressIndicator( - color = Color.Black, - modifier = Modifier.size(24.dp) - ) - } else { - 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 - ) - } - } - } - } - } -} - -// Utility function to format sats -private fun formatSats(sats: Long): String { - return when { - sats >= 100_000_000 -> String.format("%.2f BTC", sats / 100_000_000.0) - sats >= 1000 -> String.format("%,d sats", sats) - else -> "$sats sats" - } -} diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveEcashDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveEcashDialog.kt new file mode 100644 index 00000000..d2238a95 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveEcashDialog.kt @@ -0,0 +1,253 @@ +package com.bitchat.android.wallet.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.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.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.bitchat.android.wallet.data.CashuToken +import com.bitchat.android.wallet.viewmodel.WalletViewModel + +/** + * Ecash (Cashu) token receive dialog content + */ +@Composable +fun ReceiveEcashDialog( + viewModel: WalletViewModel, + decodedToken: CashuToken?, + isLoading: Boolean +) { + val token by viewModel.tokenInput.observeAsState("") + val clipboardManager = LocalClipboardManager.current + + if (decodedToken != null) { + // Show decoded token info and receive button + 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 = "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 = WalletUtils.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)) + Text( + text = "Memo: ${decodedToken.memo}", + color = Color.Gray, + fontSize = 14.sp, + fontFamily = FontFamily.Monospace, + textAlign = TextAlign.Center + ) + } + } + } + + // Receive button + Button( + onClick = { + viewModel.receiveCashuToken(decodedToken.token) + }, + enabled = !isLoading, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851), + disabledContainerColor = Color(0xFF2A2A2A) + ), + shape = RoundedCornerShape(16.dp) + ) { + if (isLoading) { + CircularProgressIndicator( + color = Color.Black, + modifier = Modifier.size(24.dp) + ) + } else { + 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( + modifier = Modifier.fillMaxWidth() + ) { + // Token input field + OutlinedTextField( + value = token, + onValueChange = { + viewModel.setTokenInput(it) + }, + label = { + Text( + text = "Cashu Token", + fontFamily = FontFamily.Monospace + ) + }, + placeholder = { + Text( + text = "cashu...", + fontFamily = FontFamily.Monospace, + color = Color.Gray + ) + }, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp), + minLines = 3, + maxLines = 4, + shape = RoundedCornerShape(16.dp), + trailingIcon = { + IconButton( + onClick = { + // TODO: Implement QR code scanning + } + ) { + Icon( + imageVector = Icons.Filled.QrCode, + contentDescription = "Scan QR", + tint = Color(0xFF00C851) + ) + } + } + ) + + // Paste button + Button( + onClick = { + clipboardManager.getText()?.text?.let { clipText -> + if (clipText.startsWith("cashu")) { + viewModel.setTokenInput(clipText) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + border = BorderStroke( + 2.dp, + Color(0xFF00C851) + ), + shape = RoundedCornerShape(16.dp) + ) { + 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 + ) + } + } + } + } +} + + \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveLightningDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveLightningDialog.kt new file mode 100644 index 00000000..cfb265f1 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/wallet/ui/ReceiveLightningDialog.kt @@ -0,0 +1,275 @@ +package com.bitchat.android.wallet.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.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.unit.dp +import androidx.compose.ui.unit.sp +import com.bitchat.android.wallet.data.MintQuote +import com.bitchat.android.wallet.viewmodel.WalletViewModel + +/** + * Lightning invoice receive dialog content + */ +@Composable +fun ReceiveLightningDialog( + viewModel: WalletViewModel, + currentMintQuote: MintQuote?, + isLoading: Boolean +) { + var amount by remember { mutableStateOf("") } + var description by remember { mutableStateOf("") } + val clipboardManager = LocalClipboardManager.current + + if (currentMintQuote != null) { + // Show Lightning invoice QR code and details + 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 = WalletUtils.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) + .padding(bottom = 24.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + QRCodeCanvas( + text = currentMintQuote.request, + modifier = Modifier.fillMaxSize() + ) + } + } + + // Copy invoice button + Button( + onClick = { + clipboardManager.setText(AnnotatedString(currentMintQuote.request)) + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + border = BorderStroke( + 2.dp, + Color(0xFFFFB000) + ), + shape = RoundedCornerShape(16.dp) + ) { + 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( + modifier = Modifier.fillMaxWidth() + ) { + // Amount input + OutlinedTextField( + value = amount, + onValueChange = { amount = it }, + label = { + Text( + text = "Amount (sats)", + fontFamily = FontFamily.Monospace + ) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + shape = RoundedCornerShape(16.dp) + ) + + // Description input + OutlinedTextField( + value = description, + onValueChange = { description = it }, + label = { + Text( + text = "Description (optional)", + fontFamily = FontFamily.Monospace + ) + }, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 32.dp), + shape = RoundedCornerShape(16.dp) + ) + + // Create invoice button + Button( + onClick = { + amount.toLongOrNull()?.let { amountSats -> + viewModel.createMintQuote(amountSats, description.ifEmpty { null }) + } + }, + enabled = !isLoading && amount.toLongOrNull()?.let { it > 0 } == true, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851), + disabledContainerColor = Color(0xFF2A2A2A) + ), + shape = RoundedCornerShape(16.dp) + ) { + if (isLoading) { + CircularProgressIndicator( + color = Color.Black, + modifier = Modifier.size(24.dp) + ) + } else { + 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 + ) + } + } + } + } + } +} + + \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/SendDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/SendDialog.kt index a47ebf18..65ea264f 100644 --- a/app/src/main/java/com/bitchat/android/wallet/ui/SendDialog.kt +++ b/app/src/main/java/com/bitchat/android/wallet/ui/SendDialog.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -16,15 +14,10 @@ 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 com.bitchat.android.wallet.data.MeltQuote import com.bitchat.android.wallet.viewmodel.WalletViewModel /** @@ -116,7 +109,7 @@ fun SendView( ) Text( - text = formatSats(balance), + text = WalletUtils.formatSats(balance), color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold, @@ -184,7 +177,7 @@ fun SendView( // Content based on send type when (sendType) { WalletViewModel.SendType.CASHU -> { - CashuSendContent( + SendEcashDialog( viewModel = viewModel, generatedToken = generatedToken, isLoading = isLoading, @@ -192,7 +185,7 @@ fun SendView( ) } WalletViewModel.SendType.LIGHTNING -> { - LightningSendContent( + SendLightningDialog( viewModel = viewModel, currentMeltQuote = currentMeltQuote, isLoading = isLoading, @@ -204,529 +197,4 @@ fun SendView( } } -@Composable -private fun CashuSendContent( - viewModel: WalletViewModel, - generatedToken: String?, - isLoading: Boolean, - maxAmount: Long -) { - var amount by remember { mutableStateOf("") } - var memo by remember { mutableStateOf("") } - val clipboardManager = LocalClipboardManager.current - - if (generatedToken != null) { - // Show generated token - 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 = "CASHU TOKEN CREATED", - color = Color(0xFF00C851), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - - Spacer(modifier = Modifier.height(24.dp)) - - // Token display - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A)) - ) { - SelectionContainer { - Text( - text = generatedToken, - color = Color.White, - fontSize = 12.sp, - fontFamily = FontFamily.Monospace, - modifier = Modifier.padding(16.dp), - textAlign = TextAlign.Center - ) - } - } - } - } - - // Copy button - Button( - onClick = { - clipboardManager.setText(AnnotatedString(generatedToken)) - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851) - ), - shape = RoundedCornerShape(16.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Filled.ContentCopy, - contentDescription = "Copy", - tint = Color.Black, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "COPY TOKEN", - color = Color.Black, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - } - } - } - } else { - // Show input form - Column( - modifier = Modifier.fillMaxWidth() - ) { - // Amount input - OutlinedTextField( - value = amount, - onValueChange = { amount = it }, - label = { - Text( - text = "Amount (sats)", - fontFamily = FontFamily.Monospace - ) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp), - shape = RoundedCornerShape(16.dp) - ) - - // Memo input - OutlinedTextField( - value = memo, - onValueChange = { memo = it }, - label = { - Text( - text = "Memo (optional)", - fontFamily = FontFamily.Monospace - ) - }, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 32.dp), - shape = RoundedCornerShape(16.dp) - ) - - // Create token button - Button( - onClick = { - amount.toLongOrNull()?.let { amountSats -> - viewModel.createCashuToken(amountSats, memo.ifEmpty { null }) - } - }, - enabled = !isLoading && amount.toLongOrNull()?.let { it > 0 && it <= maxAmount } == true, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851), - disabledContainerColor = Color(0xFF2A2A2A) - ), - shape = RoundedCornerShape(16.dp) - ) { - if (isLoading) { - CircularProgressIndicator( - color = Color.Black, - modifier = Modifier.size(24.dp) - ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Filled.Toll, - contentDescription = "Create Token", - tint = Color.Black, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "CREATE TOKEN", - color = Color.Black, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - } - } - } - } - } -} -@Composable -private fun LightningSendContent( - viewModel: WalletViewModel, - currentMeltQuote: MeltQuote?, - isLoading: Boolean, - maxAmount: Long -) { - var invoice by remember { mutableStateOf("") } - val clipboardManager = LocalClipboardManager.current - - if (currentMeltQuote != null) { - // Show quote and pay button - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Quote 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.FlashOn, - contentDescription = "Lightning", - tint = Color(0xFFFFB000), - modifier = Modifier.size(48.dp) - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "LIGHTNING PAYMENT QUOTE", - color = Color(0xFFFFB000), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - - Spacer(modifier = Modifier.height(24.dp)) - - // Quote details - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.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, - fontSize = 14.sp - ) - Text( - text = formatSats(currentMeltQuote.amount.toLong()), - color = Color.White, - fontFamily = FontFamily.Monospace, - fontSize = 14.sp, - fontWeight = FontWeight.Bold - ) - } - - if (currentMeltQuote.feeReserve.toLong() > 0) { - Spacer(modifier = Modifier.height(8.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "Fee:", - color = Color.Gray, - fontFamily = FontFamily.Monospace, - fontSize = 14.sp - ) - Text( - text = formatSats(currentMeltQuote.feeReserve.toLong()), - color = Color.White, - fontFamily = FontFamily.Monospace, - fontSize = 14.sp, - fontWeight = FontWeight.Bold - ) - } - - Spacer(modifier = Modifier.height(8.dp)) - HorizontalDivider(color = Color.Gray) - Spacer(modifier = Modifier.height(8.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "Total:", - color = Color.Gray, - fontFamily = FontFamily.Monospace, - fontSize = 14.sp, - fontWeight = FontWeight.Bold - ) - Text( - text = formatSats(currentMeltQuote.amount.toLong() + currentMeltQuote.feeReserve.toLong()), - color = Color.White, - fontFamily = FontFamily.Monospace, - fontSize = 14.sp, - fontWeight = FontWeight.Bold - ) - } - } - } - } - } - } - - // Pay button - Button( - onClick = { - viewModel.payLightningInvoice(currentMeltQuote.id) - }, - enabled = !isLoading, - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851), - disabledContainerColor = Color(0xFF2A2A2A) - ), - shape = RoundedCornerShape(16.dp) - ) { - if (isLoading) { - CircularProgressIndicator( - color = Color.Black, - modifier = Modifier.size(24.dp) - ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Filled.FlashOn, - contentDescription = "Pay Invoice", - tint = Color.Black, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "PAY INVOICE", - color = Color.Black, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - } - } - } - } - } else { - // Show invoice input - Column( - modifier = Modifier.fillMaxWidth() - ) { - // Invoice input field - OutlinedTextField( - value = invoice, - onValueChange = { invoice = it }, - label = { - Text( - text = "Lightning Invoice", - fontFamily = FontFamily.Monospace - ) - }, - placeholder = { - Text( - text = "lnbc...", - fontFamily = FontFamily.Monospace, - color = Color.Gray - ) - }, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Color(0xFF00C851), - focusedLabelColor = Color(0xFF00C851), - unfocusedBorderColor = Color(0xFF2A2A2A), - unfocusedLabelColor = Color.Gray, - unfocusedTextColor = Color.White, - focusedTextColor = Color.White - ), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 24.dp), - minLines = 3, - maxLines = 4, - shape = RoundedCornerShape(16.dp), - trailingIcon = { - IconButton( - onClick = { - // TODO: Implement QR code scanning - } - ) { - Icon( - imageVector = Icons.Filled.QrCode, - contentDescription = "Scan QR", - tint = Color(0xFF00C851) - ) - } - } - ) - - // Paste button - Button( - onClick = { - clipboardManager.getText()?.text?.let { clipText -> - if (clipText.startsWith("lnbc") || clipText.startsWith("lnbtb")) { - invoice = clipText - } - } - }, - modifier = Modifier - .fillMaxWidth() - .height(56.dp) - .padding(bottom = 16.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - border = androidx.compose.foundation.BorderStroke( - 2.dp, - Color(0xFF00C851) - ), - shape = RoundedCornerShape(16.dp) - ) { - 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 - ) - } - } - - // Get quote button - Button( - onClick = { - if (invoice.isNotEmpty()) { - viewModel.createMeltQuote(invoice) - } - }, - enabled = !isLoading && invoice.isNotEmpty(), - modifier = Modifier - .fillMaxWidth() - .height(56.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00C851), - disabledContainerColor = Color(0xFF2A2A2A) - ), - shape = RoundedCornerShape(16.dp) - ) { - if (isLoading) { - CircularProgressIndicator( - color = Color.Black, - modifier = Modifier.size(24.dp) - ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Filled.Assessment, - contentDescription = "Get Quote", - tint = Color.Black, - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = "GET QUOTE", - color = Color.Black, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - letterSpacing = 1.sp - ) - } - } - } - } - } -} - -// Utility function to format sats -private fun formatSats(sats: Long): String { - return when { - sats >= 100_000_000 -> String.format("%.2f BTC", sats / 100_000_000.0) - sats >= 1000 -> String.format("%,d sats", sats) - else -> "$sats sats" - } -} diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/SendEcashDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/SendEcashDialog.kt new file mode 100644 index 00000000..1295c5a4 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/wallet/ui/SendEcashDialog.kt @@ -0,0 +1,232 @@ +package com.bitchat.android.wallet.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.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 com.bitchat.android.wallet.viewmodel.WalletViewModel + +/** + * Ecash (Cashu) token send dialog content + */ +@Composable +fun SendEcashDialog( + viewModel: WalletViewModel, + generatedToken: String?, + isLoading: Boolean, + maxAmount: Long +) { + var amount by remember { mutableStateOf("") } + var memo by remember { mutableStateOf("") } + val clipboardManager = LocalClipboardManager.current + + if (generatedToken != null) { + // Show generated token + 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 = "CASHU TOKEN CREATED", + color = Color(0xFF00C851), + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // Token display + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A)) + ) { + SelectionContainer { + Text( + text = generatedToken, + color = Color.White, + fontSize = 12.sp, + fontFamily = FontFamily.Monospace, + modifier = Modifier.padding(16.dp), + textAlign = TextAlign.Center + ) + } + } + } + } + + // Copy button + Button( + onClick = { + clipboardManager.setText(AnnotatedString(generatedToken)) + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851) + ), + shape = RoundedCornerShape(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.ContentCopy, + contentDescription = "Copy", + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "COPY TOKEN", + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + } + } + } + } else { + // Show input form + Column( + modifier = Modifier.fillMaxWidth() + ) { + // Amount input + OutlinedTextField( + value = amount, + onValueChange = { amount = it }, + label = { + Text( + text = "Amount (sats)", + fontFamily = FontFamily.Monospace + ) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + shape = RoundedCornerShape(16.dp) + ) + + // Memo input + OutlinedTextField( + value = memo, + onValueChange = { memo = it }, + label = { + Text( + text = "Memo (optional)", + fontFamily = FontFamily.Monospace + ) + }, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 32.dp), + shape = RoundedCornerShape(16.dp) + ) + + // Create token button + Button( + onClick = { + amount.toLongOrNull()?.let { amountSats -> + viewModel.createCashuToken(amountSats, memo.ifEmpty { null }) + } + }, + enabled = !isLoading && amount.toLongOrNull()?.let { it > 0 && it <= maxAmount } == true, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851), + disabledContainerColor = Color(0xFF2A2A2A) + ), + shape = RoundedCornerShape(16.dp) + ) { + if (isLoading) { + CircularProgressIndicator( + color = Color.Black, + modifier = Modifier.size(24.dp) + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.Toll, + contentDescription = "Create Token", + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "CREATE TOKEN", + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/SendLightningDialog.kt b/app/src/main/java/com/bitchat/android/wallet/ui/SendLightningDialog.kt new file mode 100644 index 00000000..d1e6199d --- /dev/null +++ b/app/src/main/java/com/bitchat/android/wallet/ui/SendLightningDialog.kt @@ -0,0 +1,333 @@ +package com.bitchat.android.wallet.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.bitchat.android.wallet.data.MeltQuote +import com.bitchat.android.wallet.viewmodel.WalletViewModel + +/** + * Lightning payment send dialog content + */ +@Composable +fun SendLightningDialog( + viewModel: WalletViewModel, + currentMeltQuote: MeltQuote?, + isLoading: Boolean, + maxAmount: Long +) { + var invoice by remember { mutableStateOf("") } + val clipboardManager = LocalClipboardManager.current + + if (currentMeltQuote != null) { + // Show quote and pay button + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Quote 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.FlashOn, + contentDescription = "Lightning", + tint = Color(0xFFFFB000), + modifier = Modifier.size(48.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "LIGHTNING PAYMENT QUOTE", + color = Color(0xFFFFB000), + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // Quote details + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.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, + fontSize = 14.sp + ) + Text( + text = WalletUtils.formatSats(currentMeltQuote.amount.toLong()), + color = Color.White, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + + if (currentMeltQuote.feeReserve.toLong() > 0) { + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Fee:", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp + ) + Text( + text = WalletUtils.formatSats(currentMeltQuote.feeReserve.toLong()), + color = Color.White, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + HorizontalDivider(color = Color.Gray) + Spacer(modifier = Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Total:", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = WalletUtils.formatSats(currentMeltQuote.amount.toLong() + currentMeltQuote.feeReserve.toLong()), + color = Color.White, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + } + } + } + } + } + } + + // Pay button + Button( + onClick = { + viewModel.payLightningInvoice(currentMeltQuote.id) + }, + enabled = !isLoading, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851), + disabledContainerColor = Color(0xFF2A2A2A) + ), + shape = RoundedCornerShape(16.dp) + ) { + if (isLoading) { + CircularProgressIndicator( + color = Color.Black, + modifier = Modifier.size(24.dp) + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.FlashOn, + contentDescription = "Pay Invoice", + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "PAY INVOICE", + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + } + } + } + } + } else { + // Show invoice input + Column( + modifier = Modifier.fillMaxWidth() + ) { + // Invoice input field + OutlinedTextField( + value = invoice, + onValueChange = { invoice = it }, + label = { + Text( + text = "Lightning Invoice", + fontFamily = FontFamily.Monospace + ) + }, + placeholder = { + Text( + text = "lnbc...", + fontFamily = FontFamily.Monospace, + color = Color.Gray + ) + }, + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF00C851), + focusedLabelColor = Color(0xFF00C851), + unfocusedBorderColor = Color(0xFF2A2A2A), + unfocusedLabelColor = Color.Gray, + unfocusedTextColor = Color.White, + focusedTextColor = Color.White + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp), + minLines = 3, + maxLines = 4, + shape = RoundedCornerShape(16.dp), + trailingIcon = { + IconButton( + onClick = { + // TODO: Implement QR code scanning + } + ) { + Icon( + imageVector = Icons.Filled.QrCode, + contentDescription = "Scan QR", + tint = Color(0xFF00C851) + ) + } + } + ) + + // Paste button + Button( + onClick = { + clipboardManager.getText()?.text?.let { clipText -> + if (clipText.startsWith("lnbc") || clipText.startsWith("lnbtb")) { + invoice = clipText + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .padding(bottom = 16.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + border = BorderStroke( + 2.dp, + Color(0xFF00C851) + ), + shape = RoundedCornerShape(16.dp) + ) { + 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 + ) + } + } + + // Get quote button + Button( + onClick = { + if (invoice.isNotEmpty()) { + viewModel.createMeltQuote(invoice) + } + }, + enabled = !isLoading && invoice.isNotEmpty(), + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF00C851), + disabledContainerColor = Color(0xFF2A2A2A) + ), + shape = RoundedCornerShape(16.dp) + ) { + if (isLoading) { + CircularProgressIndicator( + color = Color.Black, + modifier = Modifier.size(24.dp) + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.Assessment, + contentDescription = "Get Quote", + tint = Color.Black, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "GET QUOTE", + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.Monospace, + letterSpacing = 1.sp + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/wallet/ui/WalletUtils.kt b/app/src/main/java/com/bitchat/android/wallet/ui/WalletUtils.kt new file mode 100644 index 00000000..3120138f --- /dev/null +++ b/app/src/main/java/com/bitchat/android/wallet/ui/WalletUtils.kt @@ -0,0 +1,17 @@ +package com.bitchat.android.wallet.ui + +/** + * Utility functions for wallet UI components + */ +object WalletUtils { + /** + * Format satoshis for display + */ + fun formatSats(sats: Long): String { + return when { + sats >= 100_000_000 -> String.format("%.2f BTC", sats / 100_000_000.0) + sats >= 1000 -> String.format("%,d sats", sats) + else -> "$sats sats" + } + } +} \ No newline at end of file