package com.example.gudariwallet.ui import android.content.ClipboardManager import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.example.gudariwallet.MainActivity import com.example.gudariwallet.util.SendInputType import kotlinx.coroutines.delay // ── Tab destinations ────────────────────────────────────────────────────────── sealed class TabItem(val route: String, val label: String, val icon: ImageVector) { data object Receive : TabItem("receive", "Receive", Icons.Filled.QrCode) data object Send : TabItem("send", "Send", Icons.AutoMirrored.Filled.Send) } private const val ROUTE_HOME = "home" private const val ROUTE_SCANNER = "scanner" private const val ROUTE_HISTORY = "history" private const val ROUTE_PAYMENT_DETAIL = "payment_detail/{paymentHash}" @Composable fun WalletScreen( vm : WalletViewModel, pendingPaymentHash: MutableState, pendingPaymentUri : MutableState // SESSION 09: deep link / intent URI ) { val navController = rememberNavController() val context = LocalContext.current val historyFactory = remember { HistoryViewModelFactory( repo = vm.repo, aliasRepo = vm.aliasRepo, paymentCache = vm.paymentCache, fiatCurrency = vm.selectedCurrency, fiatSatsPerUnit = vm.fiatSatsPerUnit ) } val historyVm: HistoryViewModel = viewModel(factory = historyFactory) val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route val showBottomBar = currentRoute != ROUTE_PAYMENT_DETAIL && currentRoute != ROUTE_SCANNER && currentRoute != ROUTE_HISTORY val isFullScreenDestination = currentRoute == ROUTE_HISTORY || currentRoute == ROUTE_PAYMENT_DETAIL || currentRoute == ROUTE_SCANNER // ── Notification tap ────────────────────────────────────────────────────── val hash = pendingPaymentHash.value LaunchedEffect(hash) { if (hash != null) { navController.navigate("payment_detail/$hash") { launchSingleTop = true } pendingPaymentHash.value = null } } val activity = context as MainActivity LaunchedEffect(Unit) { activity.windowFocusEvents.collect { val cm = context.getSystemService(ClipboardManager::class.java) val text = cm?.primaryClip?.getItemAt(0) ?.coerceToText(context)?.toString() vm.checkClipboard(text) } } LaunchedEffect(Unit) { vm.navigateToReceive.collect { Log.d("WalletScreen", "LNURL-WITHDRAW [NAV RECEIVED] switching to Receive tab") navController.navigate(TabItem.Receive.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } } val intentUri = pendingPaymentUri.value LaunchedEffect(intentUri) { if (intentUri != null) { Log.d("WalletScreen", "INTENT [URI] deep link received: $intentUri") pendingPaymentUri.value = null // consume immediately vm.scan(intentUri) navController.navigate(TabItem.Send.route) { launchSingleTop = true } } } val clipboardOffer by vm.clipboardOfferState.collectAsStateWithLifecycle() // Auto-dismiss banner after 8 seconds LaunchedEffect(clipboardOffer) { if (clipboardOffer is ClipboardOfferState.Detected) { delay(8_000) vm.dismissClipboardOffer() } } Scaffold( bottomBar = { if (showBottomBar) { // ── Floating island nav bar: Receive | [Scan] | Send ────────── Box( modifier = Modifier .fillMaxWidth() .navigationBarsPadding() .padding(horizontal = 24.dp) .padding(bottom = 16.dp), contentAlignment = Alignment.Center ) { Surface( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surfaceContainer, tonalElevation = 6.dp, shadowElevation = 6.dp, modifier = Modifier.fillMaxWidth() ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { // Receive NavigationBarItem( selected = currentRoute == TabItem.Receive.route, icon = { Icon(TabItem.Receive.icon, contentDescription = TabItem.Receive.label) }, label = { Text(TabItem.Receive.label) }, modifier = Modifier.weight(1f), onClick = { if (currentRoute != TabItem.Receive.route) { navController.navigate(TabItem.Receive.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } } ) // Scan — centre, elevated, not a nav destination Box( modifier = Modifier.weight(1f), contentAlignment = Alignment.Center ) { FilledIconButton( onClick = { navController.navigate(ROUTE_SCANNER) }, modifier = Modifier.size(52.dp), shape = CircleShape ) { Icon( imageVector = Icons.Filled.QrCodeScanner, contentDescription = "Scan", modifier = Modifier.size(26.dp) ) } } // Send NavigationBarItem( selected = currentRoute == TabItem.Send.route, icon = { Icon(TabItem.Send.icon, contentDescription = TabItem.Send.label) }, label = { Text(TabItem.Send.label) }, modifier = Modifier.weight(1f), onClick = { if (currentRoute != TabItem.Send.route) { navController.navigate(TabItem.Send.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } } ) } } } } } ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(bottom = innerPadding.calculateBottomPadding()) ) { NavHost( navController = navController, startDestination = ROUTE_HOME, modifier = if (isFullScreenDestination) Modifier.fillMaxSize() else Modifier .fillMaxSize() .statusBarsPadding() ) { composable(ROUTE_HOME) { HomeTab( vm = vm, onHistoryClick = { navController.navigate(ROUTE_HISTORY) { launchSingleTop = true } } ) } composable(TabItem.Receive.route) { ReceiveScreen(vm) } composable(TabItem.Send.route) { SendScreen(vm) } // Scanner — full screen, no bottom chrome composable(ROUTE_SCANNER) { QrScannerScreen( onScanned = { scanned -> navController.popBackStack() vm.scan(scanned) navController.navigate(TabItem.Send.route) { launchSingleTop = true } }, onDismiss = { navController.popBackStack() } ) } // History — full screen, no bottom chrome, close → home composable( ROUTE_HISTORY, enterTransition = { fadeIn(animationSpec = tween(150)) }, popExitTransition = { fadeOut(animationSpec = tween(150)) } ) { HistoryScreen( vm = historyVm, onClose = { navController.popBackStack(ROUTE_HOME, inclusive = false) }, onPaymentClick = { payment -> navController.navigate("payment_detail/${payment.paymentHash}") } ) } // Payment detail — back → history (natural back stack) composable( route = ROUTE_PAYMENT_DETAIL, arguments = listOf(navArgument("paymentHash") { type = NavType.StringType }) ) { backStackEntry -> val paymentHash = backStackEntry.arguments?.getString("paymentHash") ?: "" val historyState by historyVm.state.collectAsState() val payment = (historyState as? HistoryState.Success) ?.payments ?.firstOrNull { it.paymentHash == paymentHash } if (payment != null) { PaymentDetailScreen( payment = payment, vm = historyVm, onBack = { navController.popBackStack() } ) } else { LaunchedEffect(paymentHash) { historyVm.refresh() } Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } } } // ── Clipboard banner — floats over content, slides in from top ──── AnimatedVisibility( visible = clipboardOffer is ClipboardOfferState.Detected, enter = slideInVertically(initialOffsetY = { -it }), exit = slideOutVertically(targetOffsetY = { -it }), modifier = Modifier .align(Alignment.TopCenter) .zIndex(1f) .fillMaxWidth() .statusBarsPadding() ) { if (clipboardOffer is ClipboardOfferState.Detected) { val offer = clipboardOffer as ClipboardOfferState.Detected val label = when (offer.inputType) { is SendInputType.Bolt11 -> "Invoice" is SendInputType.Lnurl -> "LNURL" is SendInputType.LightningAddress -> "Lightning Address" else -> "Payment" } val subLabel = when (offer.inputType) { is SendInputType.Lnurl -> "Tap to open" else -> "Tap to pay" } Surface( color = MaterialTheme.colorScheme.primaryContainer, modifier = Modifier .fillMaxWidth() .clickable { vm.dismissClipboardOffer() vm.scan(offer.raw) navController.navigate(TabItem.Send.route) { launchSingleTop = true } } ) { Row( modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.ContentPaste, contentDescription = null) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { Text( "$label detected in clipboard", style = MaterialTheme.typography.bodyMedium ) Text( subLabel, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f) ) } IconButton(onClick = { vm.dismissClipboardOffer() }) { Icon(Icons.Default.Close, contentDescription = "Dismiss") } } } } } } } }