From e09c40e981683c1559bc1f49d576e4d61db8b2c7 Mon Sep 17 00:00:00 2001 From: rasputin Date: Sat, 6 Jun 2026 00:31:25 +0200 Subject: [PATCH] feat: localisation with lyricist --- .../gudariwallet/i18n/EnStrings.kt | 18 ++++ .../gudariwallet/i18n/EsStrings.kt | 18 ++++ .../bitcointxoko/gudariwallet/i18n/Strings.kt | 20 ++++- .../gudariwallet/ui/WalletScreen.kt | 82 +++++++++---------- 4 files changed, 93 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt index acff33c..6fdc48e 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EnStrings.kt @@ -4,6 +4,24 @@ import cafe.adriel.lyricist.LyricistStrings @LyricistStrings(languageTag = "en", default = true) val EnHomeStrings = AppStrings( + // ── WalletScreen ────────────────────────────────────────────────────────── + tabReceive = "Receive", + tabSend = "Send", + scanContentDescription = "Scan", + dismissContentDescription = "Dismiss", + clipboardInvoiceDetected = "Invoice detected in clipboard", + clipboardLnurlDetected = "LNURL detected in clipboard", + clipboardAddressDetected = "Lightning Address detected in clipboard", + clipboardPaymentDetected = "Payment detected in clipboard", + clipboardTapToOpen = "Tap to open", + clipboardTapToPay = "Tap to pay", + nfcInvoiceDetected = "Invoice detected via NFC", + nfcLnurlDetected = "LNURL detected via NFC", + nfcAddressDetected = "Lightning Address detected via NFC", + nfcPaymentDetected = "Payment detected via NFC", + nfcTapToOpen = "Tap to open", + nfcTapToPay = "Tap to pay", + // ── HomeTab ─────────────────────────────────────────────────────────────── loadingBalance = "Loading balance…", sats = "sats", diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt index 8dbd7cd..ba5c842 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/EsStrings.kt @@ -4,6 +4,24 @@ import cafe.adriel.lyricist.LyricistStrings @LyricistStrings(languageTag = "es") val EsHomeStrings = AppStrings( + // ── WalletScreen ────────────────────────────────────────────────────────── + tabReceive = "Recibir", + tabSend = "Enviar", + scanContentDescription = "Escanear", + dismissContentDescription = "Cerrar", + clipboardInvoiceDetected = "Factura detectada en el portapapeles", + clipboardLnurlDetected = "LNURL detectado en el portapapeles", + clipboardAddressDetected = "Dirección Lightning detectada en el portapapeles", + clipboardPaymentDetected = "Pago detectado en el portapapeles", + clipboardTapToOpen = "Toca para abrir", + clipboardTapToPay = "Toca para pagar", + nfcInvoiceDetected = "Factura detectada por NFC", + nfcLnurlDetected = "LNURL detectado por NFC", + nfcAddressDetected = "Dirección Lightning detectada por NFC", + nfcPaymentDetected = "Pago detectado por NFC", + nfcTapToOpen = "Toca para abrir", + nfcTapToPay = "Toca para pagar", + // ── HomeTab ─────────────────────────────────────────────────────────────── loadingBalance = "Cargando saldo…", sats = "sats", diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt index 510f98a..55a8d16 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/i18n/Strings.kt @@ -1,7 +1,25 @@ package com.bitcointxoko.gudariwallet.i18n data class AppStrings( - // ── Loading state ────────────────────────────────────────────────────────── + // ── WalletScreen ────────────────────────────────────────────────────────── + val tabReceive : String, + val tabSend : String, + val scanContentDescription : String, + val dismissContentDescription : String, + val clipboardInvoiceDetected : String, + val clipboardLnurlDetected : String, + val clipboardAddressDetected : String, + val clipboardPaymentDetected : String, + val clipboardTapToOpen : String, + val clipboardTapToPay : String, + val nfcInvoiceDetected : String, + val nfcLnurlDetected : String, + val nfcAddressDetected : String, + val nfcPaymentDetected : String, + val nfcTapToOpen : String, + val nfcTapToPay : String, + + // ── HomeTab ─────────────────────────────────────────────────────────────── val loadingBalance: String, // ── Balance display ──────────────────────────────────────────────────────── diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt index aba8e6f..e1f888d 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/WalletScreen.kt @@ -56,6 +56,7 @@ import androidx.navigation.navArgument import cafe.adriel.lyricist.Lyricist import com.bitcointxoko.gudariwallet.MainActivity import com.bitcointxoko.gudariwallet.i18n.AppStrings +import com.bitcointxoko.gudariwallet.LocalAppStrings import com.bitcointxoko.gudariwallet.ui.history.HistoryScreen import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModel import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModelFactory @@ -67,10 +68,12 @@ import com.bitcointxoko.gudariwallet.util.SendInputType import kotlinx.coroutines.delay // ── Tab destinations ────────────────────────────────────────────────────────── +// Note: label and icon are now resolved at the call site via LocalAppStrings, +// so the sealed class carries only the stable route string and icon vector. -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) +sealed class TabItem(val route: String, val icon: ImageVector) { + data object Receive : TabItem("receive", Icons.Filled.QrCode) + data object Send : TabItem("send", Icons.AutoMirrored.Filled.Send) } private const val ROUTE_HOME = "home" @@ -81,11 +84,12 @@ private const val ROUTE_PAYMENT_DETAIL = "payment_detail/{checkingId}" @Composable fun WalletScreen( vm : WalletViewModel, - nfcVm : NfcViewModel, // ← new + nfcVm : NfcViewModel, pendingPaymentHash: MutableState, pendingPaymentUri : MutableState, lyricist : Lyricist ) { + val strings = LocalAppStrings.current val navController = rememberNavController() val context = LocalContext.current val historyFactory = remember { @@ -179,7 +183,6 @@ fun WalletScreen( Scaffold( bottomBar = { if (showBottomBar) { - // ── Floating island nav bar: Receive | [Scan] | Send ────────── Box( modifier = Modifier .fillMaxWidth() @@ -204,8 +207,8 @@ fun WalletScreen( // Receive NavigationBarItem( selected = currentRoute == TabItem.Receive.route, - icon = { Icon(TabItem.Receive.icon, contentDescription = TabItem.Receive.label) }, - label = { Text(TabItem.Receive.label) }, + icon = { Icon(TabItem.Receive.icon, contentDescription = strings.tabReceive) }, // ← localized + label = { Text(strings.tabReceive) }, // ← localized modifier = Modifier.weight(1f), onClick = { if (currentRoute != TabItem.Receive.route) { @@ -239,7 +242,7 @@ fun WalletScreen( ) { Icon( imageVector = Icons.Filled.QrCodeScanner, - contentDescription = "Scan", + contentDescription = strings.scanContentDescription, // ← localized modifier = Modifier.size(26.dp) ) } @@ -248,8 +251,8 @@ fun WalletScreen( // Send NavigationBarItem( selected = currentRoute == TabItem.Send.route, - icon = { Icon(TabItem.Send.icon, contentDescription = TabItem.Send.label) }, - label = { Text(TabItem.Send.label) }, + icon = { Icon(TabItem.Send.icon, contentDescription = strings.tabSend) }, // ← localized + label = { Text(strings.tabSend) }, // ← localized modifier = Modifier.weight(1f), onClick = { if (currentRoute != TabItem.Send.route) { @@ -298,7 +301,7 @@ fun WalletScreen( composable(TabItem.Receive.route) { ReceiveScreen( vm = vm, - nfcVm = nfcVm, // ← new + nfcVm = nfcVm, onNavigateToPaymentDetail = { checkingId -> navController.navigate("payment_detail/$checkingId") } @@ -306,7 +309,7 @@ fun WalletScreen( } composable(TabItem.Send.route) { SendScreen( - vm = vm, + vm = vm, nfcVm = nfcVm, onViewDetails = { checkingId, record -> historyVm.primePayment(record) @@ -324,13 +327,12 @@ fun WalletScreen( vm.scan(scanned) val wentBack = navController.popBackStack(TabItem.Send.route, inclusive = false) if (!wentBack) { - // Scan was triggered from a non-Send tab — navigate to Send navController.navigate(TabItem.Send.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true - restoreState = false // don't restore old Idle state; VM already has new state + restoreState = false } } }, @@ -341,11 +343,11 @@ fun WalletScreen( // History — full screen, no bottom chrome, close → home composable( ROUTE_HISTORY, - enterTransition = { fadeIn(animationSpec = tween(150)) }, - popExitTransition = { fadeOut(animationSpec = tween(150)) } + enterTransition = { fadeIn(animationSpec = tween(150)) }, + popExitTransition = { fadeOut(animationSpec = tween(150)) } ) { HistoryScreen( - vm = historyVm, + vm = historyVm, onClose = { navController.popBackStack(ROUTE_HOME, inclusive = false) }, @@ -360,7 +362,7 @@ fun WalletScreen( route = ROUTE_PAYMENT_DETAIL, arguments = listOf(navArgument("checkingId") { type = NavType.StringType }) ) { backStackEntry -> - val checkingId = backStackEntry.arguments?.getString("checkingId") ?: "" + val checkingId = backStackEntry.arguments?.getString("checkingId") ?: "" val historyState by historyVm.filteredState.collectAsStateWithLifecycle() val fallbackState by historyVm.state.collectAsStateWithLifecycle() val payment = remember(historyState, fallbackState, checkingId) { @@ -394,21 +396,21 @@ fun WalletScreen( exit = slideOutVertically(targetOffsetY = { -it }), modifier = Modifier .align(Alignment.TopCenter) - .zIndex(2f) // above NFC banner + .zIndex(2f) .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" + is SendInputType.Bolt11 -> strings.clipboardInvoiceDetected // ← localized + is SendInputType.Lnurl -> strings.clipboardLnurlDetected // ← localized + is SendInputType.LightningAddress -> strings.clipboardAddressDetected // ← localized + else -> strings.clipboardPaymentDetected // ← localized } val subLabel = when (offer.inputType) { - is SendInputType.Lnurl -> "Tap to open" - else -> "Tap to pay" + is SendInputType.Lnurl -> strings.clipboardTapToOpen // ← localized + else -> strings.clipboardTapToPay // ← localized } Surface( color = MaterialTheme.colorScheme.primaryContainer, @@ -429,10 +431,7 @@ fun WalletScreen( 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(label, style = MaterialTheme.typography.bodyMedium) Text( subLabel, style = MaterialTheme.typography.bodySmall, @@ -440,7 +439,7 @@ fun WalletScreen( ) } IconButton(onClick = { vm.dismissClipboardOffer() }) { - Icon(Icons.Default.Close, contentDescription = "Dismiss") + Icon(Icons.Default.Close, contentDescription = strings.dismissContentDescription) // ← localized } } } @@ -448,29 +447,27 @@ fun WalletScreen( } // ── NFC banner — floats over content, slides in from top ────────── - // Shown when an NFC tag is tapped while the app is foregrounded. - // Mirrors the clipboard banner pattern exactly. AnimatedVisibility( visible = nfcOffer is NfcOfferState.Detected, enter = slideInVertically(initialOffsetY = { -it }), exit = slideOutVertically(targetOffsetY = { -it }), modifier = Modifier .align(Alignment.TopCenter) - .zIndex(1f) // below clipboard banner if both show + .zIndex(1f) .fillMaxWidth() .statusBarsPadding() ) { if (nfcOffer is NfcOfferState.Detected) { val offer = nfcOffer as NfcOfferState.Detected val label = when (offer.inputType) { - is SendInputType.Bolt11 -> "Invoice" - is SendInputType.Lnurl -> "LNURL" - is SendInputType.LightningAddress -> "Lightning Address" - else -> "Payment" + is SendInputType.Bolt11 -> strings.nfcInvoiceDetected // ← localized + is SendInputType.Lnurl -> strings.nfcLnurlDetected // ← localized + is SendInputType.LightningAddress -> strings.nfcAddressDetected // ← localized + else -> strings.nfcPaymentDetected // ← localized } val subLabel = when (offer.inputType) { - is SendInputType.Lnurl -> "Tap to open" - else -> "Tap to pay" + is SendInputType.Lnurl -> strings.nfcTapToOpen // ← localized + else -> strings.nfcTapToPay // ← localized } Surface( color = MaterialTheme.colorScheme.secondaryContainer, @@ -491,10 +488,7 @@ fun WalletScreen( Icon(Icons.Default.Nfc, contentDescription = null) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { - Text( - "$label detected via NFC", - style = MaterialTheme.typography.bodyMedium - ) + Text(label, style = MaterialTheme.typography.bodyMedium) Text( subLabel, style = MaterialTheme.typography.bodySmall, @@ -502,7 +496,7 @@ fun WalletScreen( ) } IconButton(onClick = { nfcVm.dismissNfcOffer() }) { - Icon(Icons.Default.Close, contentDescription = "Dismiss") + Icon(Icons.Default.Close, contentDescription = strings.dismissContentDescription) // ← localized } } }