feat: localisation with lyricist
This commit is contained in:
@@ -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<String?>,
|
||||
pendingPaymentUri : MutableState<String?>,
|
||||
lyricist : Lyricist<AppStrings>
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user