mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 22:05:26 +00:00
refactor af
This commit is contained in:
@@ -171,15 +171,15 @@ class BluetoothGattServerManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (characteristic.uuid == CHARACTERISTIC_UUID) {
|
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)
|
val packet = BitchatPacket.fromBinaryData(value)
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
val peerID = String(packet.senderID).replace("\u0000", "")
|
val peerID = String(packet.senderID).replace("\u0000", "")
|
||||||
Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID")
|
Log.d(TAG, "Server: Parsed packet type ${packet.type} from $peerID")
|
||||||
delegate?.onPacketReceived(packet, peerID, device)
|
delegate?.onPacketReceived(packet, peerID, device)
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Server: Failed to parse packet from ${device.address}, size: ${value.size} bytes")
|
// 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: Packet data: ${value.joinToString(" ") { "%02x".format(it) }}")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseNeeded) {
|
if (responseNeeded) {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ 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
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -16,16 +14,10 @@ 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.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
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.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.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 com.bitchat.android.wallet.data.MintQuote
|
|
||||||
import com.bitchat.android.wallet.data.CashuToken
|
|
||||||
import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,14 +142,14 @@ fun ReceiveView(
|
|||||||
// Content based on receive type
|
// Content based on receive type
|
||||||
when (receiveType) {
|
when (receiveType) {
|
||||||
WalletViewModel.ReceiveType.CASHU -> {
|
WalletViewModel.ReceiveType.CASHU -> {
|
||||||
CashuReceiveContent(
|
ReceiveEcashDialog(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
decodedToken = decodedToken,
|
decodedToken = decodedToken,
|
||||||
isLoading = isLoading
|
isLoading = isLoading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
WalletViewModel.ReceiveType.LIGHTNING -> {
|
WalletViewModel.ReceiveType.LIGHTNING -> {
|
||||||
LightningReceiveContent(
|
ReceiveLightningDialog(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
currentMintQuote = currentMintQuote,
|
currentMintQuote = currentMintQuote,
|
||||||
isLoading = isLoading
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -4,8 +4,6 @@ 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
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -16,15 +14,10 @@ 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.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
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.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.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 com.bitchat.android.wallet.data.MeltQuote
|
|
||||||
import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,7 +109,7 @@ fun SendView(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = formatSats(balance),
|
text = WalletUtils.formatSats(balance),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -184,7 +177,7 @@ fun SendView(
|
|||||||
// Content based on send type
|
// Content based on send type
|
||||||
when (sendType) {
|
when (sendType) {
|
||||||
WalletViewModel.SendType.CASHU -> {
|
WalletViewModel.SendType.CASHU -> {
|
||||||
CashuSendContent(
|
SendEcashDialog(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
generatedToken = generatedToken,
|
generatedToken = generatedToken,
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
@@ -192,7 +185,7 @@ fun SendView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
WalletViewModel.SendType.LIGHTNING -> {
|
WalletViewModel.SendType.LIGHTNING -> {
|
||||||
LightningSendContent(
|
SendLightningDialog(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
currentMeltQuote = currentMeltQuote,
|
currentMeltQuote = currentMeltQuote,
|
||||||
isLoading = isLoading,
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user