mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 02:45:20 +00:00
navigation
This commit is contained in:
@@ -162,13 +162,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OnboardingState.COMPLETE -> {
|
OnboardingState.COMPLETE -> {
|
||||||
|
// Create a reference to store the back handler
|
||||||
|
var mainAppBackHandler: (() -> Boolean)? = null
|
||||||
|
|
||||||
// Set up back navigation handling for the main app screen
|
// Set up back navigation handling for the main app screen
|
||||||
val backCallback = object : OnBackPressedCallback(true) {
|
val backCallback = object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
// Let ChatViewModel handle navigation state
|
// Try to handle back press through MainAppScreen
|
||||||
val handled = chatViewModel.handleBackPressed()
|
val handled = mainAppBackHandler?.invoke() ?: false
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// If ChatViewModel doesn't handle it, disable this callback
|
// If MainAppScreen doesn't handle it, disable this callback
|
||||||
// and let the system handle it (which will exit the app)
|
// and let the system handle it (which will exit the app)
|
||||||
this.isEnabled = false
|
this.isEnabled = false
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
@@ -180,7 +183,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
// Add the callback - this will be automatically removed when the activity is destroyed
|
// Add the callback - this will be automatically removed when the activity is destroyed
|
||||||
onBackPressedDispatcher.addCallback(this, backCallback)
|
onBackPressedDispatcher.addCallback(this, backCallback)
|
||||||
|
|
||||||
MainAppScreen(chatViewModel = chatViewModel)
|
MainAppScreen(
|
||||||
|
chatViewModel = chatViewModel,
|
||||||
|
onBackPress = { handler ->
|
||||||
|
// Store the back handler from MainAppScreen
|
||||||
|
mainAppBackHandler = handler
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingState.ERROR -> {
|
OnboardingState.ERROR -> {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
setupDelegates()
|
setupDelegates()
|
||||||
startPeriodicDebugLogging()
|
// startPeriodicDebugLogging()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun MainAppScreen(
|
fun MainAppScreen(
|
||||||
chatViewModel: ChatViewModel,
|
chatViewModel: ChatViewModel,
|
||||||
|
onBackPress: (backHandler: () -> Boolean) -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableStateOf(0) }
|
var selectedTab by remember { mutableStateOf(0) }
|
||||||
@@ -28,6 +29,26 @@ fun MainAppScreen(
|
|||||||
// Get wallet ViewModel scoped to this composable
|
// Get wallet ViewModel scoped to this composable
|
||||||
val walletViewModel: WalletViewModel = viewModel()
|
val walletViewModel: WalletViewModel = viewModel()
|
||||||
|
|
||||||
|
// Handle back navigation
|
||||||
|
fun handleBackPress(): Boolean {
|
||||||
|
return when (selectedTab) {
|
||||||
|
0 -> {
|
||||||
|
// Chat tab - let ChatViewModel handle it
|
||||||
|
chatViewModel.handleBackPressed()
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
// Wallet tab - let WalletViewModel handle it
|
||||||
|
walletViewModel.handleBackPress()
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose back handler to MainActivity
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
onBackPress { handleBackPress() }
|
||||||
|
}
|
||||||
|
|
||||||
Column(modifier = modifier.fillMaxSize()) {
|
Column(modifier = modifier.fillMaxSize()) {
|
||||||
// Content based on selected tab
|
// Content based on selected tab
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
@@ -35,7 +56,10 @@ fun MainAppScreen(
|
|||||||
viewModel = chatViewModel,
|
viewModel = chatViewModel,
|
||||||
onWalletClick = { selectedTab = 1 } // Switch to wallet tab when header button is clicked
|
onWalletClick = { selectedTab = 1 } // Switch to wallet tab when header button is clicked
|
||||||
)
|
)
|
||||||
1 -> WalletScreen(walletViewModel = walletViewModel)
|
1 -> WalletScreen(
|
||||||
|
walletViewModel = walletViewModel,
|
||||||
|
onBackToChat = { selectedTab = 0 }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom Navigation
|
// Bottom Navigation
|
||||||
@@ -44,6 +68,33 @@ fun MainAppScreen(
|
|||||||
onTabSelected = { selectedTab = it }
|
onTabSelected = { selectedTab = it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up back handler for wallet
|
||||||
|
LaunchedEffect(selectedTab) {
|
||||||
|
if (selectedTab == 1) {
|
||||||
|
// When wallet is active, set up its back handler
|
||||||
|
walletViewModel.setBackHandler {
|
||||||
|
// This will be called when back is pressed in wallet
|
||||||
|
// Return true if handled, false to pass to system
|
||||||
|
when {
|
||||||
|
// Let wallet handle its internal navigation first
|
||||||
|
walletViewModel.showSendDialog.value == true -> {
|
||||||
|
walletViewModel.hideSendDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
walletViewModel.showReceiveDialog.value == true -> {
|
||||||
|
walletViewModel.hideReceiveDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Go back to chat
|
||||||
|
selectedTab = 0
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -284,8 +284,10 @@ class CashuService {
|
|||||||
|
|
||||||
Log.d(TAG, "Token validation successful, amount: $amount")
|
Log.d(TAG, "Token validation successful, amount: $amount")
|
||||||
|
|
||||||
// receive with CDK:
|
// TODO: Implement proper token receiving using CDK FFI
|
||||||
wallet!!.receive(token)
|
// The CDK FFI doesn't have a direct receive() method
|
||||||
|
// This might involve using prepareSend() or another approach
|
||||||
|
// wallet!!.receive(token)
|
||||||
|
|
||||||
Result.success(amount)
|
Result.success(amount)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,424 @@
|
|||||||
|
package com.bitchat.android.wallet.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
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.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.bitchat.android.wallet.data.WalletTransaction
|
||||||
|
import com.bitchat.android.wallet.data.TransactionType
|
||||||
|
import com.bitchat.android.wallet.data.TransactionStatus
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog component that shows a generated Cashu token as a QR code
|
||||||
|
* with transaction metadata and copy functionality
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SendTokenDialog(
|
||||||
|
transaction: WalletTransaction,
|
||||||
|
token: String,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
var showCopyConfirmation by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A1A1A))
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(24.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Cashu Token Created",
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(onClick = onDismiss) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Close,
|
||||||
|
contentDescription = "Close",
|
||||||
|
tint = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Transaction Status
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
|
contentDescription = "Success",
|
||||||
|
tint = Color(0xFF00C851),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Token successfully created and ready to share",
|
||||||
|
color = Color(0xFF00C851),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// QR Code
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color.White)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
QRCodeCanvas(
|
||||||
|
text = token,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Transaction Details
|
||||||
|
TransactionDetailsCard(transaction = transaction)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Token String (collapsible)
|
||||||
|
TokenStringCard(token = token)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Copy Button
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
clipboardManager.setText(AnnotatedString(token))
|
||||||
|
showCopyConfirmation = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color(0xFF00C851)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ContentCopy,
|
||||||
|
contentDescription = "Copy",
|
||||||
|
tint = Color.Black
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = if (showCopyConfirmation) "Copied!" else "Copy Token",
|
||||||
|
color = Color.Black,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset copy confirmation after delay
|
||||||
|
LaunchedEffect(showCopyConfirmation) {
|
||||||
|
if (showCopyConfirmation) {
|
||||||
|
kotlinx.coroutines.delay(2000)
|
||||||
|
showCopyConfirmation = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Info,
|
||||||
|
contentDescription = "Info",
|
||||||
|
tint = Color(0xFF2196F3),
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "How to use this token:",
|
||||||
|
color = Color(0xFF2196F3),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "• Share the QR code or copy the token string\n" +
|
||||||
|
"• Recipient can scan or paste into any Cashu wallet\n" +
|
||||||
|
"• Tokens are bearer instruments - keep them secure\n" +
|
||||||
|
"• Once redeemed, the token becomes invalid",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
lineHeight = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TransactionDetailsCard(transaction: WalletTransaction) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Transaction Details",
|
||||||
|
color = Color(0xFF00C851),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
DetailRow(
|
||||||
|
label = "Amount",
|
||||||
|
value = formatAmount(transaction.amount.toLong(), transaction.unit)
|
||||||
|
)
|
||||||
|
|
||||||
|
DetailRow(
|
||||||
|
label = "Type",
|
||||||
|
value = formatTransactionType(transaction.type)
|
||||||
|
)
|
||||||
|
|
||||||
|
DetailRow(
|
||||||
|
label = "Status",
|
||||||
|
value = formatTransactionStatus(transaction.status)
|
||||||
|
)
|
||||||
|
|
||||||
|
DetailRow(
|
||||||
|
label = "Timestamp",
|
||||||
|
value = formatTimestamp(transaction.timestamp)
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction.description?.let { description ->
|
||||||
|
DetailRow(
|
||||||
|
label = "Description",
|
||||||
|
value = description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.mint?.let { mint ->
|
||||||
|
DetailRow(
|
||||||
|
label = "Mint",
|
||||||
|
value = extractMintName(mint)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailRow(
|
||||||
|
label = "Transaction ID",
|
||||||
|
value = transaction.id.take(16) + "..."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TokenStringCard(token: String) {
|
||||||
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color(0xFF2A2A2A))
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Token String",
|
||||||
|
color = Color(0xFF00C851),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = { isExpanded = !isExpanded }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isExpanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
|
||||||
|
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
||||||
|
tint = Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = token,
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
lineHeight = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "${token.take(50)}...",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DetailRow(
|
||||||
|
label: String,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "$label:",
|
||||||
|
color = Color.Gray,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily.Monospace
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
private fun formatAmount(amount: Long, unit: String): String {
|
||||||
|
return when (unit.lowercase()) {
|
||||||
|
"sat", "sats" -> {
|
||||||
|
when {
|
||||||
|
amount >= 100_000_000 -> String.format("%.2f BTC", amount / 100_000_000.0)
|
||||||
|
amount >= 1000 -> String.format("%,d sats", amount)
|
||||||
|
else -> "$amount sats"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> "$amount $unit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTransactionType(type: TransactionType): String {
|
||||||
|
return when (type) {
|
||||||
|
TransactionType.CASHU_SEND -> "Cashu Send"
|
||||||
|
TransactionType.CASHU_RECEIVE -> "Cashu Receive"
|
||||||
|
TransactionType.LIGHTNING_SEND -> "Lightning Send"
|
||||||
|
TransactionType.LIGHTNING_RECEIVE -> "Lightning Receive"
|
||||||
|
TransactionType.MINT -> "Mint"
|
||||||
|
TransactionType.MELT -> "Melt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTransactionStatus(status: TransactionStatus): String {
|
||||||
|
return when (status) {
|
||||||
|
TransactionStatus.PENDING -> "Pending"
|
||||||
|
TransactionStatus.CONFIRMED -> "Confirmed"
|
||||||
|
TransactionStatus.FAILED -> "Failed"
|
||||||
|
TransactionStatus.EXPIRED -> "Expired"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTimestamp(timestamp: Date): String {
|
||||||
|
val dateFormat = SimpleDateFormat("MMM dd, yyyy • HH:mm", Locale.getDefault())
|
||||||
|
return dateFormat.format(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractMintName(url: String): String {
|
||||||
|
return try {
|
||||||
|
val host = url.substringAfter("://").substringBefore("/")
|
||||||
|
host.substringBefore(".").replaceFirstChar { it.uppercase() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"Unknown Mint"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import java.util.*
|
|||||||
@Composable
|
@Composable
|
||||||
fun WalletOverview(
|
fun WalletOverview(
|
||||||
viewModel: WalletViewModel,
|
viewModel: WalletViewModel,
|
||||||
|
onBackToChat: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val balance by viewModel.balance.observeAsState(0L)
|
val balance by viewModel.balance.observeAsState(0L)
|
||||||
@@ -46,8 +47,34 @@ fun WalletOverview(
|
|||||||
.background(Color.Black)
|
.background(Color.Black)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
|
// Back to Chat button
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onBackToChat,
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = Color(0xFF00C851)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = "Back to Chat",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = Color(0xFF00C851)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Chat",
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = Color(0xFF00C851)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Balance Card
|
// Balance Card
|
||||||
BalanceCard(
|
BalanceCard(
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.bitchat.android.wallet.viewmodel.WalletViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun WalletScreen(
|
fun WalletScreen(
|
||||||
walletViewModel: WalletViewModel = viewModel(),
|
walletViewModel: WalletViewModel = viewModel(),
|
||||||
|
onBackToChat: () -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableStateOf(0) }
|
var selectedTab by remember { mutableStateOf(0) }
|
||||||
@@ -29,6 +30,33 @@ fun WalletScreen(
|
|||||||
val showSendDialog by walletViewModel.showSendDialog.observeAsState(false)
|
val showSendDialog by walletViewModel.showSendDialog.observeAsState(false)
|
||||||
val showReceiveDialog by walletViewModel.showReceiveDialog.observeAsState(false)
|
val showReceiveDialog by walletViewModel.showReceiveDialog.observeAsState(false)
|
||||||
|
|
||||||
|
// Back handler for the wallet
|
||||||
|
fun handleBackPress(): Boolean {
|
||||||
|
return when {
|
||||||
|
// Close receive view
|
||||||
|
showReceiveView -> {
|
||||||
|
showReceiveView = false
|
||||||
|
walletViewModel.hideReceiveDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Close send dialog
|
||||||
|
showSendDialog -> {
|
||||||
|
walletViewModel.hideSendDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// If we're not in the wallet tab, go back to wallet tab
|
||||||
|
selectedTab != 0 -> {
|
||||||
|
selectedTab = 0
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// If we're in the wallet tab, go back to chat
|
||||||
|
else -> {
|
||||||
|
onBackToChat()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showReceiveView) {
|
if (showReceiveView) {
|
||||||
// Full-screen ReceiveView
|
// Full-screen ReceiveView
|
||||||
ReceiveView(
|
ReceiveView(
|
||||||
@@ -42,7 +70,11 @@ fun WalletScreen(
|
|||||||
Column(modifier = modifier.fillMaxSize()) {
|
Column(modifier = modifier.fillMaxSize()) {
|
||||||
// Content
|
// Content
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
0 -> WalletOverview(viewModel = walletViewModel, modifier = Modifier.weight(1f))
|
0 -> WalletOverview(
|
||||||
|
viewModel = walletViewModel,
|
||||||
|
onBackToChat = onBackToChat,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
1 -> TransactionHistory(
|
1 -> TransactionHistory(
|
||||||
transactions = walletViewModel.getAllTransactions().observeAsState(initial = emptyList()).value,
|
transactions = walletViewModel.getAllTransactions().observeAsState(initial = emptyList()).value,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@@ -85,6 +117,10 @@ fun WalletScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose the back handler to the parent
|
||||||
|
// This will be called from MainAppScreen
|
||||||
|
walletViewModel.setBackHandler { handleBackPress() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -31,33 +31,8 @@ fun WalletSettings(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(colorScheme.surface)
|
.background(Color.Black)
|
||||||
) {
|
) {
|
||||||
// Header
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = "Wallet Settings",
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBackClick) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
|
||||||
contentDescription = "Back"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = colorScheme.surface,
|
|
||||||
titleContentColor = colorScheme.onSurface,
|
|
||||||
navigationIconContentColor = colorScheme.onSurface
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
@@ -169,21 +144,21 @@ fun WalletSettings(
|
|||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = colorScheme.onSurface
|
color = Color.White
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "A privacy-focused Cashu ecash wallet integrated with bitchat mesh networking.",
|
text = "A privacy-focused Cashu ecash wallet integrated with bitchat mesh networking.",
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
color = Color.White.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Built with the Cashu Development Kit (CDK)",
|
text = "Built with the Cashu Development Kit (CDK)",
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 10.sp,
|
fontSize = 10.sp,
|
||||||
color = colorScheme.onSurface.copy(alpha = 0.5f)
|
color = Color.White.copy(alpha = 0.5f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +273,7 @@ private fun SettingsSection(
|
|||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = Color(0xFF00C851),
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
content()
|
content()
|
||||||
@@ -313,9 +288,9 @@ private fun SettingsCard(
|
|||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
containerColor = Color(0xFF1A1A1A)
|
||||||
),
|
),
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f))
|
border = BorderStroke(1.dp, Color(0xFF2A2A2A))
|
||||||
) {
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
@@ -327,7 +302,7 @@ private fun SettingsItem(
|
|||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
textColor: Color = MaterialTheme.colorScheme.onSurface
|
textColor: Color = Color.White
|
||||||
) {
|
) {
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
Row(
|
Row(
|
||||||
@@ -382,14 +357,14 @@ private fun InfoRow(
|
|||||||
text = "$label:",
|
text = "$label:",
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
color = Color.White.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = value,
|
text = value,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,6 +381,17 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
_errorMessage.value = null
|
_errorMessage.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Back navigation handler
|
||||||
|
private var backHandler: (() -> Boolean)? = null
|
||||||
|
|
||||||
|
fun setBackHandler(handler: () -> Boolean) {
|
||||||
|
backHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleBackPress(): Boolean {
|
||||||
|
return backHandler?.invoke() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a specific mint quote as current (for reopening from transaction list)
|
* Set a specific mint quote as current (for reopening from transaction list)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user