navigation

This commit is contained in:
callebtc
2025-07-15 02:31:36 +02:00
parent f922a84fcc
commit f01cb686fc
9 changed files with 581 additions and 46 deletions
@@ -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)
*/ */