feat: localisation with lyricist
This commit is contained in:
@@ -4,6 +4,24 @@ import cafe.adriel.lyricist.LyricistStrings
|
|||||||
|
|
||||||
@LyricistStrings(languageTag = "en", default = true)
|
@LyricistStrings(languageTag = "en", default = true)
|
||||||
val EnHomeStrings = AppStrings(
|
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 ───────────────────────────────────────────────────────────────
|
// ── HomeTab ───────────────────────────────────────────────────────────────
|
||||||
loadingBalance = "Loading balance…",
|
loadingBalance = "Loading balance…",
|
||||||
sats = "sats",
|
sats = "sats",
|
||||||
|
|||||||
@@ -4,6 +4,24 @@ import cafe.adriel.lyricist.LyricistStrings
|
|||||||
|
|
||||||
@LyricistStrings(languageTag = "es")
|
@LyricistStrings(languageTag = "es")
|
||||||
val EsHomeStrings = AppStrings(
|
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 ───────────────────────────────────────────────────────────────
|
// ── HomeTab ───────────────────────────────────────────────────────────────
|
||||||
loadingBalance = "Cargando saldo…",
|
loadingBalance = "Cargando saldo…",
|
||||||
sats = "sats",
|
sats = "sats",
|
||||||
|
|||||||
@@ -1,7 +1,25 @@
|
|||||||
package com.bitcointxoko.gudariwallet.i18n
|
package com.bitcointxoko.gudariwallet.i18n
|
||||||
|
|
||||||
data class AppStrings(
|
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,
|
val loadingBalance: String,
|
||||||
|
|
||||||
// ── Balance display ────────────────────────────────────────────────────────
|
// ── Balance display ────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import androidx.navigation.navArgument
|
|||||||
import cafe.adriel.lyricist.Lyricist
|
import cafe.adriel.lyricist.Lyricist
|
||||||
import com.bitcointxoko.gudariwallet.MainActivity
|
import com.bitcointxoko.gudariwallet.MainActivity
|
||||||
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
||||||
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import com.bitcointxoko.gudariwallet.ui.history.HistoryScreen
|
import com.bitcointxoko.gudariwallet.ui.history.HistoryScreen
|
||||||
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModel
|
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModel
|
||||||
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModelFactory
|
import com.bitcointxoko.gudariwallet.ui.history.HistoryViewModelFactory
|
||||||
@@ -67,10 +68,12 @@ import com.bitcointxoko.gudariwallet.util.SendInputType
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
// ── Tab destinations ──────────────────────────────────────────────────────────
|
// ── 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) {
|
sealed class TabItem(val route: String, val icon: ImageVector) {
|
||||||
data object Receive : TabItem("receive", "Receive", Icons.Filled.QrCode)
|
data object Receive : TabItem("receive", Icons.Filled.QrCode)
|
||||||
data object Send : TabItem("send", "Send", Icons.AutoMirrored.Filled.Send)
|
data object Send : TabItem("send", Icons.AutoMirrored.Filled.Send)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val ROUTE_HOME = "home"
|
private const val ROUTE_HOME = "home"
|
||||||
@@ -81,11 +84,12 @@ private const val ROUTE_PAYMENT_DETAIL = "payment_detail/{checkingId}"
|
|||||||
@Composable
|
@Composable
|
||||||
fun WalletScreen(
|
fun WalletScreen(
|
||||||
vm : WalletViewModel,
|
vm : WalletViewModel,
|
||||||
nfcVm : NfcViewModel, // ← new
|
nfcVm : NfcViewModel,
|
||||||
pendingPaymentHash: MutableState<String?>,
|
pendingPaymentHash: MutableState<String?>,
|
||||||
pendingPaymentUri : MutableState<String?>,
|
pendingPaymentUri : MutableState<String?>,
|
||||||
lyricist : Lyricist<AppStrings>
|
lyricist : Lyricist<AppStrings>
|
||||||
) {
|
) {
|
||||||
|
val strings = LocalAppStrings.current
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val historyFactory = remember {
|
val historyFactory = remember {
|
||||||
@@ -179,7 +183,6 @@ fun WalletScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
if (showBottomBar) {
|
if (showBottomBar) {
|
||||||
// ── Floating island nav bar: Receive | [Scan] | Send ──────────
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -204,8 +207,8 @@ fun WalletScreen(
|
|||||||
// Receive
|
// Receive
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = currentRoute == TabItem.Receive.route,
|
selected = currentRoute == TabItem.Receive.route,
|
||||||
icon = { Icon(TabItem.Receive.icon, contentDescription = TabItem.Receive.label) },
|
icon = { Icon(TabItem.Receive.icon, contentDescription = strings.tabReceive) }, // ← localized
|
||||||
label = { Text(TabItem.Receive.label) },
|
label = { Text(strings.tabReceive) }, // ← localized
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (currentRoute != TabItem.Receive.route) {
|
if (currentRoute != TabItem.Receive.route) {
|
||||||
@@ -239,7 +242,7 @@ fun WalletScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.QrCodeScanner,
|
imageVector = Icons.Filled.QrCodeScanner,
|
||||||
contentDescription = "Scan",
|
contentDescription = strings.scanContentDescription, // ← localized
|
||||||
modifier = Modifier.size(26.dp)
|
modifier = Modifier.size(26.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -248,8 +251,8 @@ fun WalletScreen(
|
|||||||
// Send
|
// Send
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = currentRoute == TabItem.Send.route,
|
selected = currentRoute == TabItem.Send.route,
|
||||||
icon = { Icon(TabItem.Send.icon, contentDescription = TabItem.Send.label) },
|
icon = { Icon(TabItem.Send.icon, contentDescription = strings.tabSend) }, // ← localized
|
||||||
label = { Text(TabItem.Send.label) },
|
label = { Text(strings.tabSend) }, // ← localized
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (currentRoute != TabItem.Send.route) {
|
if (currentRoute != TabItem.Send.route) {
|
||||||
@@ -298,7 +301,7 @@ fun WalletScreen(
|
|||||||
composable(TabItem.Receive.route) {
|
composable(TabItem.Receive.route) {
|
||||||
ReceiveScreen(
|
ReceiveScreen(
|
||||||
vm = vm,
|
vm = vm,
|
||||||
nfcVm = nfcVm, // ← new
|
nfcVm = nfcVm,
|
||||||
onNavigateToPaymentDetail = { checkingId ->
|
onNavigateToPaymentDetail = { checkingId ->
|
||||||
navController.navigate("payment_detail/$checkingId")
|
navController.navigate("payment_detail/$checkingId")
|
||||||
}
|
}
|
||||||
@@ -324,13 +327,12 @@ fun WalletScreen(
|
|||||||
vm.scan(scanned)
|
vm.scan(scanned)
|
||||||
val wentBack = navController.popBackStack(TabItem.Send.route, inclusive = false)
|
val wentBack = navController.popBackStack(TabItem.Send.route, inclusive = false)
|
||||||
if (!wentBack) {
|
if (!wentBack) {
|
||||||
// Scan was triggered from a non-Send tab — navigate to Send
|
|
||||||
navController.navigate(TabItem.Send.route) {
|
navController.navigate(TabItem.Send.route) {
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
saveState = true
|
saveState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
restoreState = false // don't restore old Idle state; VM already has new state
|
restoreState = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -394,21 +396,21 @@ fun WalletScreen(
|
|||||||
exit = slideOutVertically(targetOffsetY = { -it }),
|
exit = slideOutVertically(targetOffsetY = { -it }),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.zIndex(2f) // above NFC banner
|
.zIndex(2f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
) {
|
) {
|
||||||
if (clipboardOffer is ClipboardOfferState.Detected) {
|
if (clipboardOffer is ClipboardOfferState.Detected) {
|
||||||
val offer = clipboardOffer as ClipboardOfferState.Detected
|
val offer = clipboardOffer as ClipboardOfferState.Detected
|
||||||
val label = when (offer.inputType) {
|
val label = when (offer.inputType) {
|
||||||
is SendInputType.Bolt11 -> "Invoice"
|
is SendInputType.Bolt11 -> strings.clipboardInvoiceDetected // ← localized
|
||||||
is SendInputType.Lnurl -> "LNURL"
|
is SendInputType.Lnurl -> strings.clipboardLnurlDetected // ← localized
|
||||||
is SendInputType.LightningAddress -> "Lightning Address"
|
is SendInputType.LightningAddress -> strings.clipboardAddressDetected // ← localized
|
||||||
else -> "Payment"
|
else -> strings.clipboardPaymentDetected // ← localized
|
||||||
}
|
}
|
||||||
val subLabel = when (offer.inputType) {
|
val subLabel = when (offer.inputType) {
|
||||||
is SendInputType.Lnurl -> "Tap to open"
|
is SendInputType.Lnurl -> strings.clipboardTapToOpen // ← localized
|
||||||
else -> "Tap to pay"
|
else -> strings.clipboardTapToPay // ← localized
|
||||||
}
|
}
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
@@ -429,10 +431,7 @@ fun WalletScreen(
|
|||||||
Icon(Icons.Default.ContentPaste, contentDescription = null)
|
Icon(Icons.Default.ContentPaste, contentDescription = null)
|
||||||
Spacer(Modifier.width(12.dp))
|
Spacer(Modifier.width(12.dp))
|
||||||
Column(Modifier.weight(1f)) {
|
Column(Modifier.weight(1f)) {
|
||||||
Text(
|
Text(label, style = MaterialTheme.typography.bodyMedium)
|
||||||
"$label detected in clipboard",
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Text(
|
Text(
|
||||||
subLabel,
|
subLabel,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -440,7 +439,7 @@ fun WalletScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = { vm.dismissClipboardOffer() }) {
|
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 ──────────
|
// ── 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(
|
AnimatedVisibility(
|
||||||
visible = nfcOffer is NfcOfferState.Detected,
|
visible = nfcOffer is NfcOfferState.Detected,
|
||||||
enter = slideInVertically(initialOffsetY = { -it }),
|
enter = slideInVertically(initialOffsetY = { -it }),
|
||||||
exit = slideOutVertically(targetOffsetY = { -it }),
|
exit = slideOutVertically(targetOffsetY = { -it }),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.zIndex(1f) // below clipboard banner if both show
|
.zIndex(1f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
) {
|
) {
|
||||||
if (nfcOffer is NfcOfferState.Detected) {
|
if (nfcOffer is NfcOfferState.Detected) {
|
||||||
val offer = nfcOffer as NfcOfferState.Detected
|
val offer = nfcOffer as NfcOfferState.Detected
|
||||||
val label = when (offer.inputType) {
|
val label = when (offer.inputType) {
|
||||||
is SendInputType.Bolt11 -> "Invoice"
|
is SendInputType.Bolt11 -> strings.nfcInvoiceDetected // ← localized
|
||||||
is SendInputType.Lnurl -> "LNURL"
|
is SendInputType.Lnurl -> strings.nfcLnurlDetected // ← localized
|
||||||
is SendInputType.LightningAddress -> "Lightning Address"
|
is SendInputType.LightningAddress -> strings.nfcAddressDetected // ← localized
|
||||||
else -> "Payment"
|
else -> strings.nfcPaymentDetected // ← localized
|
||||||
}
|
}
|
||||||
val subLabel = when (offer.inputType) {
|
val subLabel = when (offer.inputType) {
|
||||||
is SendInputType.Lnurl -> "Tap to open"
|
is SendInputType.Lnurl -> strings.nfcTapToOpen // ← localized
|
||||||
else -> "Tap to pay"
|
else -> strings.nfcTapToPay // ← localized
|
||||||
}
|
}
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
@@ -491,10 +488,7 @@ fun WalletScreen(
|
|||||||
Icon(Icons.Default.Nfc, contentDescription = null)
|
Icon(Icons.Default.Nfc, contentDescription = null)
|
||||||
Spacer(Modifier.width(12.dp))
|
Spacer(Modifier.width(12.dp))
|
||||||
Column(Modifier.weight(1f)) {
|
Column(Modifier.weight(1f)) {
|
||||||
Text(
|
Text(label, style = MaterialTheme.typography.bodyMedium)
|
||||||
"$label detected via NFC",
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Text(
|
Text(
|
||||||
subLabel,
|
subLabel,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -502,7 +496,7 @@ fun WalletScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = { nfcVm.dismissNfcOffer() }) {
|
IconButton(onClick = { nfcVm.dismissNfcOffer() }) {
|
||||||
Icon(Icons.Default.Close, contentDescription = "Dismiss")
|
Icon(Icons.Default.Close, contentDescription = strings.dismissContentDescription) // ← localized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user