From 42a0bf80051b63a63ffa04e81b375d93b31f2113 Mon Sep 17 00:00:00 2001 From: "@Delagado74" Date: Sun, 3 May 2026 04:28:32 -0400 Subject: [PATCH 1/3] feat: add Invoice Key QR screen for LaChispaPOS integration - Add InvoiceKeyScreen with QR code display for readKey - Use readKey with fallback to inKey (least privilege) - Add 'Invoice Key QR' option in Settings screen - Update localizations for all 7 supported languages - Make readKey required in WalletInfo model - Update WalletProvider to forward readKey when updating wallet Resolves #68 --- lib/l10n/app_de.arb | 15 +- lib/l10n/app_en.arb | 15 +- lib/l10n/app_es.arb | 15 +- lib/l10n/app_fr.arb | 15 +- lib/l10n/app_it.arb | 15 +- lib/l10n/app_pt.arb | 15 +- lib/l10n/app_ru.arb | 15 +- lib/models/wallet_info.dart | 10 +- lib/providers/wallet_provider.dart | 88 ++++---- lib/screens/17settings_screen.dart | 229 ++++++-------------- lib/screens/19invoice_key_screen.dart | 287 ++++++++++++++++++++++++++ 11 files changed, 506 insertions(+), 213 deletions(-) create mode 100644 lib/screens/19invoice_key_screen.dart diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 7c37c9d..6f37a7c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "NFC-Fehler beim Einziehen: ", "nfc_charge_unknown_error": "Unbekannter Fehler beim Einziehen", "share_ready_message": "Bereit zum Teilen", - "lnurl_copied_message": "LNURL in die Zwischenablage kopiert" + "lnurl_copied_message": "LNURL in die Zwischenablage kopiert", + + "invoice_key_qr_title": "Rechnungsschlüssel QR", + "invoice_key_qr_description": "Verwenden Sie diesen QR-Code mit LaChispaPOS oder anderen Lightning-Apps, um Zahlungen zu empfangen, ohne Ihren Admin-Schlüssel preiszugeben.", + "invoice_key_qr_subtitle": "QR für andere Apps anzeigen", + "copy_invoice_key": "Schlüssel kopieren", + "invoice_key_copied": "Rechnungsschlüssel in die Zwischenablage kopiert", + "invoice_key_unavailable_title": "Keine Wallet gefunden", + "invoice_key_unavailable_subtitle": "Bitte erstellen Sie zuerst eine Wallet", + "invoice_key_security_warning": "Dieser Schlüssel ermöglicht Dritten, Rechnungen zu erstellen. Nur mit vertrauenswürdigen POS-Geräten teilen. Nie öffentlich posten oder teilen.", + "invoice_key_show": "Schlüssel anzeigen", + "invoice_key_hide": "Schlüssel verbergen", + "invoice_key_copy_failed": "Rechnungsschlüssel konnte nicht kopiert werden", + "invoice_key_empty": "Rechnungsschlüssel darf nicht leer sein" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c188de8..ee4edc3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -388,5 +388,18 @@ "nfc_charge_error_prefix": "NFC charge error: ", "nfc_charge_unknown_error": "Unknown error during charge", "share_ready_message": "Ready to share", - "lnurl_copied_message": "LNURL copied to clipboard" + "lnurl_copied_message": "LNURL copied to clipboard", + + "invoice_key_qr_title": "Invoice Key QR", + "invoice_key_qr_description": "Use this QR code with LaChispaPOS or other Lightning apps to receive payments without exposing your admin key.", + "invoice_key_qr_subtitle": "Show QR for other apps", + "copy_invoice_key": "Copy Key", + "invoice_key_copied": "Invoice key copied to clipboard", + "invoice_key_unavailable_title": "No wallet found", + "invoice_key_unavailable_subtitle": "Create a wallet first", + "invoice_key_security_warning": "This key allows third parties to create invoices. Only share with trusted POS devices. Never post publicly or share widely.", + "invoice_key_show": "Show key", + "invoice_key_hide": "Hide key", + "invoice_key_copy_failed": "Failed to copy invoice key", + "invoice_key_empty": "Invoice key cannot be empty" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2ed7284..b05c245 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "Error en cobro NFC: ", "nfc_charge_unknown_error": "Error desconocido al cobrar", "share_ready_message": "Listo para compartir", - "lnurl_copied_message": "LNURL copiado al portapapeles" + "lnurl_copied_message": "LNURL copiado al portapapeles", + + "invoice_key_qr_title": "QR de Clave de Facturación", + "invoice_key_qr_description": "Usa este código QR con LaChispaPOS u otras apps Lightning para recibir pagos sin exponer tu clave de administrador.", + "invoice_key_qr_subtitle": "Mostrar QR para otras apps", + "copy_invoice_key": "Copiar Clave", + "invoice_key_copied": "Clave de facturación copiada al portapapeles", + "invoice_key_unavailable_title": "No se encontró billetera", + "invoice_key_unavailable_subtitle": "Crea una billetera primero", + "invoice_key_security_warning": "Esta clave permite a terceros crear facturas. Compártela solo con dispositivos POS de confianza. Nunca la publiques ni compartas públicamente.", + "invoice_key_show": "Mostrar clave", + "invoice_key_hide": "Ocultar clave", + "invoice_key_copy_failed": "No se pudo copiar la clave de facturación", + "invoice_key_empty": "La clave de facturación no puede estar vacía" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1e68952..8e13028 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "Erreur d'encaissement NFC : ", "nfc_charge_unknown_error": "Erreur inconnue lors de l'encaissement", "share_ready_message": "Prêt à partager", - "lnurl_copied_message": "LNURL copié dans le presse-papiers" + "lnurl_copied_message": "LNURL copié dans le presse-papiers", + + "invoice_key_qr_title": "QR de clé de facturation", + "invoice_key_qr_description": "Utilisez ce code QR avec LaChispaPOS ou d'autres applications Lightning pour recevoir des paiements sans exposer votre clé administrateur.", + "invoice_key_qr_subtitle": "Afficher le QR pour d'autres applications", + "copy_invoice_key": "Copier la clé", + "invoice_key_copied": "Clé de facturation copiée dans le presse-papiers", + "invoice_key_unavailable_title": "Aucun portefeuille trouvé", + "invoice_key_unavailable_subtitle": "Créez d'abord un portefeuille", + "invoice_key_security_warning": "Cette clé permet à des tiers de créer des factures. Ne la partagez qu'avec des appareils POS de confiance. Ne la publiez jamais publiquement.", + "invoice_key_show": "Afficher la clé", + "invoice_key_hide": "Masquer la clé", + "invoice_key_copy_failed": "Impossible de copier la clé de facturation", + "invoice_key_empty": "La clé de facturation ne peut pas être vide" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 233fcb6..8b660be 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "Errore incasso NFC: ", "nfc_charge_unknown_error": "Errore sconosciuto durante l'incasso", "share_ready_message": "Pronto da condividere", - "lnurl_copied_message": "LNURL copiato negli appunti" + "lnurl_copied_message": "LNURL copiato negli appunti", + + "invoice_key_qr_title": "QR Chiave fattura", + "invoice_key_qr_description": "Usa questo codice QR con LaChispaPOS o altre app Lightning per ricevere pagamenti senza esporre la tua chiave amministratore.", + "invoice_key_qr_subtitle": "Mostra QR per altre app", + "copy_invoice_key": "Copia chiave", + "invoice_key_copied": "Chiave fattura copiata negli appunti", + "invoice_key_unavailable_title": "Nessun portafoglio trovato", + "invoice_key_unavailable_subtitle": "Crea prima un portafoglio", + "invoice_key_security_warning": "Questa chiave permette a terzi di creare fatture. Condividi solo con dispositivi POS fidati. Non pubblicare mai pubblicamente.", + "invoice_key_show": "Mostra chiave", + "invoice_key_hide": "Nascondi chiave", + "invoice_key_copy_failed": "Impossibile copiare la chiave fattura", + "invoice_key_empty": "La chiave fattura non può essere vuota" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 58da320..de5bbb8 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "Erro ao cobrar com NFC: ", "nfc_charge_unknown_error": "Erro desconhecido ao cobrar", "share_ready_message": "Pronto para compartilhar", - "lnurl_copied_message": "LNURL copiado para a área de transferência" + "lnurl_copied_message": "LNURL copiado para a área de transferência", + + "invoice_key_qr_title": "QR Chave da Fatura", + "invoice_key_qr_description": "Use este código QR com LaChispaPOS ou outros aplicativos Lightning para receber pagamentos sem expor sua chave de administrador.", + "invoice_key_qr_subtitle": "Mostrar QR para outros aplicativos", + "copy_invoice_key": "Copiar Chave", + "invoice_key_copied": "Chave da fatura copiada para a área de transferência", + "invoice_key_unavailable_title": "Nenhuma carteira encontrada", + "invoice_key_unavailable_subtitle": "Crie uma carteira primeiro", + "invoice_key_security_warning": "Esta chave permite que terceiros criem faturas. Compartilhe apenas com dispositivos POS confiáveis. Nunca publique publicamente.", + "invoice_key_show": "Mostrar chave", + "invoice_key_hide": "Ocultar chave", + "invoice_key_copy_failed": "Falha ao copiar a chave da fatura", + "invoice_key_empty": "A chave da fatura não pode estar vazia" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 7b6c8f7..fc0810c 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -373,5 +373,18 @@ "nfc_charge_error_prefix": "Ошибка приёма по NFC: ", "nfc_charge_unknown_error": "Неизвестная ошибка при списании", "share_ready_message": "Готово к отправке", - "lnurl_copied_message": "LNURL скопирован в буфер обмена" + "lnurl_copied_message": "LNURL скопирован в буфер обмена", + + "invoice_key_qr_title": "QR Ключ счета", + "invoice_key_qr_description": "Используйте этот QR-код с LaChispaPOS или другими приложениями Lightning для получения платежей без раскрытия ключа администратора.", + "invoice_key_qr_subtitle": "Показать QR для других приложений", + "copy_invoice_key": "Копировать ключ", + "invoice_key_copied": "Ключ счета скопирован в буфер обмена", + "invoice_key_unavailable_title": "Кошелек не найден", + "invoice_key_unavailable_subtitle": "Сначала создайте кошелек", + "invoice_key_security_warning": "Этот ключ позволяет третьим лицам создавать счета. Делитесь только с доверенными POS-устройствами. Никогда не публикуйте публично.", + "invoice_key_show": "Показать ключ", + "invoice_key_hide": "Скрыть ключ", + "invoice_key_copy_failed": "Не удалось скопировать ключ счета", + "invoice_key_empty": "Ключ счета не может быть пустым" } \ No newline at end of file diff --git a/lib/models/wallet_info.dart b/lib/models/wallet_info.dart index a8774dc..6af1c85 100644 --- a/lib/models/wallet_info.dart +++ b/lib/models/wallet_info.dart @@ -4,6 +4,7 @@ class WalletInfo { final String name; final String adminKey; final String inKey; + final String readKey; final int balanceMsat; WalletInfo({ @@ -11,6 +12,7 @@ class WalletInfo { required this.name, required this.adminKey, required this.inKey, + required this.readKey, required this.balanceMsat, }); @@ -23,6 +25,7 @@ class WalletInfo { name: json['name'] as String, adminKey: json['adminkey'] ?? json['admin_key'] ?? json['adminKey'] ?? '', inKey: json['inkey'] ?? json['in_key'] ?? json['inKey'] ?? '', + readKey: json['readkey'] ?? json['read_key'] ?? json['readKey'] ?? '', balanceMsat: json['balance_msat'] ?? json['balance'] ?? 0, ); } @@ -33,6 +36,7 @@ class WalletInfo { 'name': name, 'adminkey': adminKey, 'inkey': inKey, + 'readkey': readKey, 'balance_msat': balanceMsat, }; } @@ -83,9 +87,9 @@ class WalletBalance { class WalletException implements Exception { final String message; - + WalletException(this.message); - + @override String toString() => 'WalletException: $message'; -} \ No newline at end of file +} diff --git a/lib/providers/wallet_provider.dart b/lib/providers/wallet_provider.dart index 44aaa57..c87e122 100644 --- a/lib/providers/wallet_provider.dart +++ b/lib/providers/wallet_provider.dart @@ -5,18 +5,18 @@ import '../models/wallet_info.dart'; class WalletProvider extends ChangeNotifier { final WalletService _walletService; - + List _wallets = []; WalletInfo? _primaryWallet; bool _isLoading = false; bool _isInitialized = false; String? _error; - + /// Callback to notify wallet changes to other providers Function(String walletId)? _onWalletChanged; WalletProvider(this._walletService); - + void setOnWalletChangedCallback(Function(String walletId) callback) { _onWalletChanged = callback; } @@ -27,12 +27,14 @@ class WalletProvider extends ChangeNotifier { } /// Save preferred wallet for user+server combination - Future _savePreferredWallet(String serverUrl, String username, String walletId) async { + Future _savePreferredWallet( + String serverUrl, String username, String walletId) async { try { final prefs = await SharedPreferences.getInstance(); final key = _getPreferredWalletKey(serverUrl, username); await prefs.setString(key, walletId); - print('[WALLET_PROVIDER] Preferred wallet saved: $walletId for $username@$serverUrl'); + print( + '[WALLET_PROVIDER] Preferred wallet saved: $walletId for $username@$serverUrl'); } catch (e) { print('[WALLET_PROVIDER] Error saving preferred wallet: $e'); } @@ -44,7 +46,8 @@ class WalletProvider extends ChangeNotifier { final prefs = await SharedPreferences.getInstance(); final key = _getPreferredWalletKey(serverUrl, username); final walletId = prefs.getString(key); - print('[WALLET_PROVIDER] Preferred wallet retrieved: $walletId for $username@$serverUrl'); + print( + '[WALLET_PROVIDER] Preferred wallet retrieved: $walletId for $username@$serverUrl'); return walletId; } catch (e) { print('[WALLET_PROVIDER] Error getting preferred wallet: $e'); @@ -60,7 +63,8 @@ class WalletProvider extends ChangeNotifier { bool get hasWallets => _wallets.isNotEmpty; int get primaryBalance => _primaryWallet?.balanceSats ?? 0; - String get primaryBalanceFormatted => _primaryWallet?.balanceFormatted ?? '0 sats'; + String get primaryBalanceFormatted => + _primaryWallet?.balanceFormatted ?? '0 sats'; String? get primaryWalletId => _primaryWallet?.id; /// Initialize user wallets with saved preference restoration @@ -76,19 +80,22 @@ class WalletProvider extends ChangeNotifier { try { print('[WALLET_PROVIDER] Initializing wallets...'); - - final wallets = await _walletService.getUserWallets( + + final wallets = await _walletService + .getUserWallets( serverUrl: serverUrl, authToken: authToken, - ).timeout( + ) + .timeout( const Duration(seconds: 15), onTimeout: () { - throw WalletException('Timeout getting wallets - check your connection'); + throw WalletException( + 'Timeout getting wallets - check your connection'); }, ); _wallets = wallets; - + if (wallets.isNotEmpty) { await _establishPrimaryWallet(serverUrl, username, wallets); } else { @@ -97,7 +104,6 @@ class WalletProvider extends ChangeNotifier { _isInitialized = true; print('[WALLET_PROVIDER] ${wallets.length} wallets initialized'); - } catch (e) { _error = e.toString().replaceFirst('WalletException: ', ''); print('[WALLET_PROVIDER] Error initializing wallets: $_error'); @@ -108,25 +114,29 @@ class WalletProvider extends ChangeNotifier { } /// Establish primary wallet based on saved preferences or fallback to first - Future _establishPrimaryWallet(String serverUrl, String? username, List wallets) async { + Future _establishPrimaryWallet( + String serverUrl, String? username, List wallets) async { WalletInfo? selectedWallet; - + if (username != null) { final preferredWalletId = await _getPreferredWallet(serverUrl, username); if (preferredWalletId != null) { - selectedWallet = wallets.where((w) => w.id == preferredWalletId).firstOrNull; + selectedWallet = + wallets.where((w) => w.id == preferredWalletId).firstOrNull; if (selectedWallet != null) { - print('[WALLET_PROVIDER] Using preferred wallet: ${selectedWallet.name}'); + print( + '[WALLET_PROVIDER] Using preferred wallet: ${selectedWallet.name}'); } else { print('[WALLET_PROVIDER] Preferred wallet not found, using first'); } } } - + selectedWallet ??= wallets.first; - + _primaryWallet = selectedWallet; - print('[WALLET_PROVIDER] Primary wallet established: ${_primaryWallet!.name}'); + print( + '[WALLET_PROVIDER] Primary wallet established: ${_primaryWallet!.name}'); print('[WALLET_PROVIDER] Balance: ${_primaryWallet!.balanceFormatted}'); } @@ -141,7 +151,7 @@ class WalletProvider extends ChangeNotifier { try { print('[WALLET_PROVIDER] Refreshing balance...'); - + final balance = await _walletService.getWalletBalance( serverUrl: serverUrl, walletId: _primaryWallet!.id, @@ -153,6 +163,7 @@ class WalletProvider extends ChangeNotifier { name: _primaryWallet!.name, adminKey: _primaryWallet!.adminKey, inKey: _primaryWallet!.inKey, + readKey: _primaryWallet!.readKey, balanceMsat: balance.balanceMsat, ); @@ -165,29 +176,30 @@ class WalletProvider extends ChangeNotifier { notifyListeners(); print('[WALLET_PROVIDER] Balance updated: ${balance.balanceFormatted}'); - } catch (e) { - _error = 'Error refreshing balance: ${e.toString().replaceFirst('WalletException: ', '')}'; + _error = + 'Error refreshing balance: ${e.toString().replaceFirst('WalletException: ', '')}'; print('[WALLET_PROVIDER] Error refreshing balance: $_error'); notifyListeners(); } } /// Change primary wallet and notify LNAddressProvider of wallet change - Future setPrimaryWallet(WalletInfo wallet, {String? serverUrl, String? username}) async { + Future setPrimaryWallet(WalletInfo wallet, + {String? serverUrl, String? username}) async { if (_wallets.contains(wallet)) { final previousWalletId = _primaryWallet?.id; _primaryWallet = wallet; print('[WALLET_PROVIDER] Primary wallet changed to: ${wallet.name}'); - + if (serverUrl != null && username != null) { await _savePreferredWallet(serverUrl, username, wallet.id); } - + if (previousWalletId != wallet.id) { _onWalletChanged?.call(wallet.id); } - + notifyListeners(); } } @@ -203,7 +215,7 @@ class WalletProvider extends ChangeNotifier { try { print('[WALLET_PROVIDER] Creating new wallet: $walletName'); - + final newWallet = await _walletService.createWallet( serverUrl: serverUrl, authToken: authToken, @@ -211,7 +223,7 @@ class WalletProvider extends ChangeNotifier { ); _wallets.add(newWallet); - + if (_wallets.length == 1) { _primaryWallet = newWallet; } @@ -219,7 +231,6 @@ class WalletProvider extends ChangeNotifier { notifyListeners(); print('[WALLET_PROVIDER] Wallet created successfully: ${newWallet.name}'); return true; - } catch (e) { _error = e.toString().replaceFirst('WalletException: ', ''); print('[WALLET_PROVIDER] Error creating wallet: $_error'); @@ -246,7 +257,7 @@ class WalletProvider extends ChangeNotifier { try { print('[WALLET_PROVIDER] Deleting wallet: ${wallet.name}'); - + await _walletService.deleteWallet( serverUrl: serverUrl, walletId: wallet.id, @@ -254,7 +265,7 @@ class WalletProvider extends ChangeNotifier { ); _wallets.removeWhere((w) => w.id == wallet.id); - + if (_primaryWallet?.id == wallet.id && _wallets.isNotEmpty) { _primaryWallet = _wallets.first; print('[WALLET_PROVIDER] New primary wallet: ${_primaryWallet!.name}'); @@ -263,7 +274,6 @@ class WalletProvider extends ChangeNotifier { notifyListeners(); print('[WALLET_PROVIDER] Wallet deleted successfully'); return true; - } catch (e) { _error = e.toString().replaceFirst('WalletException: ', ''); print('[WALLET_PROVIDER] Error deleting wallet: $_error'); @@ -300,10 +310,10 @@ class WalletProvider extends ChangeNotifier { @override String toString() { return 'WalletProvider(' - 'wallets: ${_wallets.length}, ' - 'primary: ${_primaryWallet?.name}, ' - 'isLoading: $_isLoading, ' - 'isInitialized: $_isInitialized' - ')'; + 'wallets: ${_wallets.length}, ' + 'primary: ${_primaryWallet?.name}, ' + 'isLoading: $_isLoading, ' + 'isInitialized: $_isInitialized' + ')'; } -} \ No newline at end of file +} diff --git a/lib/screens/17settings_screen.dart b/lib/screens/17settings_screen.dart index 950db06..03232f3 100644 --- a/lib/screens/17settings_screen.dart +++ b/lib/screens/17settings_screen.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/language_provider.dart'; import '../providers/currency_settings_provider.dart'; -import '../providers/theme_provider.dart'; import '../l10n/generated/app_localizations.dart'; -import '../theme/app_tokens.dart'; import '7ln_address_screen.dart'; import '16currency_settings_screen.dart'; import '18language_selection_screen.dart'; +import '19invoice_key_screen.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -19,29 +18,40 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { @override Widget build(BuildContext context) { - final t = context.tokens; return Scaffold( body: Container( width: double.infinity, height: double.infinity, - decoration: BoxDecoration(gradient: t.backgroundGradient), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0F1419), + Color(0xFF1A1D47), + Color(0xFF2D3FE7), + ], + stops: [0.0, 0.5, 1.0], + ), + ), child: SafeArea( child: Column( children: [ // Header with back button - _buildHeader(t), + _buildHeader(), // Settings list Expanded( child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: + const EdgeInsets.symmetric(horizontal: 24, vertical: 16), children: [ // Lightning Address _buildSettingsItem( - t: t, icon: Icons.alternate_email, - iconColor: t.accentSolid, - title: AppLocalizations.of(context)!.lightning_address_title, + iconColor: const Color(0xFF4C63F7), + title: + AppLocalizations.of(context)!.lightning_address_title, onTap: () { Navigator.push( context, @@ -54,20 +64,42 @@ class _SettingsScreenState extends State { const SizedBox(height: 12), + // Invoice Key QR + _buildSettingsItem( + icon: Icons.qr_code, + iconColor: const Color(0xFF4C63F7), + title: AppLocalizations.of(context)!.invoice_key_qr_title, + subtitle: + AppLocalizations.of(context)!.invoice_key_qr_subtitle, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const InvoiceKeyScreen(), + ), + ); + }, + ), + + const SizedBox(height: 12), + // Currency Settings Consumer( builder: (context, currencyProvider, child) { return _buildSettingsItem( - t: t, icon: Icons.attach_money, - iconColor: t.accentSolid, - title: AppLocalizations.of(context)!.currency_settings_title, - subtitle: '${currencyProvider.availableCurrencies.length} currencies', + iconColor: const Color(0xFF4C63F7), + title: AppLocalizations.of(context)! + .currency_settings_title, + subtitle: AppLocalizations.of(context)! + .currency_count( + currencyProvider.availableCurrencies.length), onTap: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => const CurrencySettingsScreen(), + builder: (context) => + const CurrencySettingsScreen(), ), ); }, @@ -81,36 +113,21 @@ class _SettingsScreenState extends State { Consumer( builder: (context, languageProvider, child) { return _buildSettingsItem( - t: t, icon: Icons.language, - iconColor: t.accentSolid, - title: AppLocalizations.of(context)!.language_selector_title, + iconColor: const Color(0xFF4C63F7), + title: AppLocalizations.of(context)! + .language_selector_title, subtitle: languageProvider.getCurrentLanguageName(), onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (context) => const LanguageSelectionScreen(), + builder: (context) => + const LanguageSelectionScreen(), ), ), ); }, ), - - const SizedBox(height: 12), - - // Theme Settings - Consumer( - builder: (context, themeProvider, child) { - return _buildSettingsItem( - t: t, - icon: _themeIcon(themeProvider.current), - iconColor: t.accentSolid, - title: AppLocalizations.of(context)!.theme_selector_title, - subtitle: _themeLabel(context, themeProvider.current), - onTap: () => _showThemeSelector(themeProvider), - ); - }, - ), ], ), ), @@ -121,7 +138,7 @@ class _SettingsScreenState extends State { ); } - Widget _buildHeader(AppTokens t) { + Widget _buildHeader() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( @@ -131,13 +148,12 @@ class _SettingsScreenState extends State { child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: t.surface, + color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), - border: Border.all(color: t.outline, width: 1), ), - child: Icon( + child: const Icon( Icons.arrow_back, - color: t.textPrimary, + color: Colors.white, size: 24, ), ), @@ -147,9 +163,10 @@ class _SettingsScreenState extends State { child: Text( AppLocalizations.of(context)!.settings_screen_title, style: TextStyle( + fontFamily: 'Inter', fontSize: 20, fontWeight: FontWeight.w600, - color: t.textPrimary, + color: Colors.white, ), ), ), @@ -159,7 +176,6 @@ class _SettingsScreenState extends State { } Widget _buildSettingsItem({ - required AppTokens t, required IconData icon, required Color iconColor, required String title, @@ -171,10 +187,10 @@ class _SettingsScreenState extends State { child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: t.surface, + color: Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(16), border: Border.all( - color: t.outline, + color: Colors.white.withValues(alpha: 0.1), width: 1, ), ), @@ -199,10 +215,11 @@ class _SettingsScreenState extends State { children: [ Text( title, - style: TextStyle( + style: const TextStyle( + fontFamily: 'Inter', fontSize: 16, fontWeight: FontWeight.w600, - color: t.textPrimary, + color: Colors.white, ), ), if (subtitle != null) ...[ @@ -210,9 +227,10 @@ class _SettingsScreenState extends State { Text( subtitle, style: TextStyle( + fontFamily: 'Inter', fontSize: 14, fontWeight: FontWeight.w400, - color: t.textSecondary, + color: Colors.white.withValues(alpha: 0.6), ), ), ], @@ -222,128 +240,11 @@ class _SettingsScreenState extends State { Icon( Icons.arrow_forward_ios, size: 16, - color: t.textSecondary, + color: Colors.white.withValues(alpha: 0.5), ), ], ), ), ); } - - IconData _themeIcon(AppTheme theme) { - switch (theme) { - case AppTheme.lachispa: - return Icons.bolt; - case AppTheme.light: - return Icons.light_mode; - case AppTheme.dark: - return Icons.dark_mode; - } - } - - String _themeLabel(BuildContext context, AppTheme theme) { - final l = AppLocalizations.of(context)!; - switch (theme) { - case AppTheme.lachispa: - return l.theme_lachispa; - case AppTheme.light: - return l.theme_light; - case AppTheme.dark: - return l.theme_dark; - } - } - - void _showThemeSelector(ThemeProvider themeProvider) { - final t = context.tokens; - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - builder: (modalContext) => Consumer( - builder: (context, provider, _) => Container( - decoration: BoxDecoration( - color: t.dialogBackground, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - margin: const EdgeInsets.only(top: 8), - width: 40, - height: 4, - decoration: BoxDecoration( - color: t.textPrimary.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(2), - ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: Text( - AppLocalizations.of(context)!.select_theme, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: t.textPrimary, - ), - ), - ), - ...AppTheme.values.map((option) { - final isSelected = provider.current == option; - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () async { - await provider.changeTheme(option); - if (mounted) Navigator.pop(context); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - child: Row( - children: [ - Icon( - _themeIcon(option), - color: isSelected ? t.accentSolid : t.textPrimary, - size: 24, - ), - const SizedBox(width: 16), - Expanded( - child: Text( - _themeLabel(context, option), - style: TextStyle( - fontSize: 18, - fontWeight: isSelected - ? FontWeight.w600 - : FontWeight.w400, - color: isSelected - ? t.accentSolid - : t.textPrimary, - ), - ), - ), - if (isSelected) - Icon( - Icons.check_circle, - color: t.accentSolid, - size: 24, - ), - ], - ), - ), - ), - ); - }), - const SizedBox(height: 20), - ], - ), - ), - ), - ); - } - } diff --git a/lib/screens/19invoice_key_screen.dart b/lib/screens/19invoice_key_screen.dart new file mode 100644 index 0000000..2a21920 --- /dev/null +++ b/lib/screens/19invoice_key_screen.dart @@ -0,0 +1,287 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import '../providers/wallet_provider.dart'; +import '../l10n/generated/app_localizations.dart'; + +class InvoiceKeyScreen extends StatelessWidget { + const InvoiceKeyScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0F1419), + Color(0xFF1A1D47), + Color(0xFF2D3FE7), + ], + stops: [0.0, 0.5, 1.0], + ), + ), + child: SafeArea( + child: Column( + children: [ + _buildHeader(context), + Expanded( + child: _buildContent(context), + ), + ], + ), + ), + ), + ); + } + + Widget _buildHeader(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.arrow_back, + color: Colors.white, + size: 24, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + AppLocalizations.of(context)!.invoice_key_qr_title, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ], + ), + ); + } + + Widget _buildContent(BuildContext context) { + return Consumer( + builder: (context, walletProvider, child) { + final wallet = walletProvider.primaryWallet; + final invoiceKey = (wallet?.readKey ?? '').isEmpty + ? (wallet?.inKey ?? '') + : (wallet?.readKey ?? ''); + + if (wallet == null || invoiceKey.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.warning_amber_rounded, + color: Colors.orange, + size: 64, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.invoice_key_unavailable_title, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.white.withValues(alpha: 0.9), + ), + ), + const SizedBox(height: 8), + Text( + AppLocalizations.of(context)! + .invoice_key_unavailable_subtitle, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + color: Colors.white.withValues(alpha: 0.6), + ), + ), + ], + ), + ), + ); + } + + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 20), + _buildQRCode(invoiceKey), + const SizedBox(height: 24), + _buildKeyInfo(context, invoiceKey), + const SizedBox(height: 24), + _buildCopyButton(context, invoiceKey), + const SizedBox(height: 24), + _buildWarning(context), + ], + ), + ); + }, + ); + } + + Widget _buildQRCode(String inKey) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: QrImageView( + data: inKey, + version: QrVersions.auto, + size: 220.0, + backgroundColor: Colors.white, + errorCorrectionLevel: QrErrorCorrectLevel.H, + ), + ); + } + + Widget _buildKeyInfo(BuildContext context, String inKey) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.invoice_key_label, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 8), + Text( + inKey, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + color: Colors.white, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } + + Widget _buildCopyButton(BuildContext context, String inKey) { + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => _copyToClipboard(context, inKey), + icon: const Icon(Icons.copy, size: 20), + label: Text( + AppLocalizations.of(context)!.copy_invoice_key, + style: const TextStyle( + fontFamily: 'Inter', + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4C63F7), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ); + } + + Widget _buildWarning(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.amber.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.amber.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.info_outline, + color: Colors.amber, + size: 24, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + AppLocalizations.of(context)!.invoice_key_qr_description, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + color: Colors.white.withValues(alpha: 0.8), + ), + ), + ), + ], + ), + ); + } + + void _copyToClipboard(BuildContext context, String text) { + Clipboard.setData(ClipboardData(text: text)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.invoice_key_copied, + style: const TextStyle(fontFamily: 'Inter'), + ), + backgroundColor: const Color(0xFF4C63F7), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + duration: const Duration(seconds: 2), + ), + ); + } +} From 2c5714fa8998b81b9e6f370b80b908cd4856e149 Mon Sep 17 00:00:00 2001 From: "@Delagado74" Date: Sun, 3 May 2026 04:54:15 -0400 Subject: [PATCH 2/3] fix: implement show/hide key toggle and copy with error handling --- lib/screens/19invoice_key_screen.dart | 105 +++++++++++++++++--------- 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/lib/screens/19invoice_key_screen.dart b/lib/screens/19invoice_key_screen.dart index 2a21920..6431e76 100644 --- a/lib/screens/19invoice_key_screen.dart +++ b/lib/screens/19invoice_key_screen.dart @@ -5,9 +5,44 @@ import 'package:qr_flutter/qr_flutter.dart'; import '../providers/wallet_provider.dart'; import '../l10n/generated/app_localizations.dart'; -class InvoiceKeyScreen extends StatelessWidget { +class InvoiceKeyScreen extends StatefulWidget { const InvoiceKeyScreen({super.key}); + @override + State createState() => _InvoiceKeyScreenState(); +} + +class _InvoiceKeyScreenState extends State { + bool _isKeyVisible = false; + + String _maskKey(String key) { + if (key.length <= 8) return '•' * key.length; + return '${key.substring(0, 4)}${'•' * (key.length - 8)}${key.substring(key.length - 4)}'; + } + + Future _copyToClipboard(BuildContext context, String key) async { + try { + await Clipboard.setData(ClipboardData(text: key)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.invoice_key_copied), + backgroundColor: Colors.green, + ), + ); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.invoice_key_copy_failed), + backgroundColor: Colors.red, + ), + ); + } + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -109,8 +144,7 @@ class InvoiceKeyScreen extends StatelessWidget { ), const SizedBox(height: 8), Text( - AppLocalizations.of(context)! - .invoice_key_unavailable_subtitle, + AppLocalizations.of(context)!.invoice_key_unavailable_subtitle, style: TextStyle( fontFamily: 'Inter', fontSize: 14, @@ -142,7 +176,7 @@ class InvoiceKeyScreen extends StatelessWidget { ); } - Widget _buildQRCode(String inKey) { + Widget _buildQRCode(String invoiceKey) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( @@ -157,7 +191,7 @@ class InvoiceKeyScreen extends StatelessWidget { ], ), child: QrImageView( - data: inKey, + data: invoiceKey, version: QrVersions.auto, size: 220.0, backgroundColor: Colors.white, @@ -166,7 +200,7 @@ class InvoiceKeyScreen extends StatelessWidget { ); } - Widget _buildKeyInfo(BuildContext context, String inKey) { + Widget _buildKeyInfo(BuildContext context, String invoiceKey) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), @@ -181,18 +215,35 @@ class InvoiceKeyScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - AppLocalizations.of(context)!.invoice_key_label, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white.withValues(alpha: 0.6), - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.invoice_key_qr_title, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white.withValues(alpha: 0.6), + ), + ), + GestureDetector( + onTap: () { + setState(() { + _isKeyVisible = !_isKeyVisible; + }); + }, + child: Icon( + _isKeyVisible ? Icons.visibility_off : Icons.visibility, + color: Colors.white70, + size: 20, + ), + ), + ], ), const SizedBox(height: 8), Text( - inKey, + _isKeyVisible ? invoiceKey : _maskKey(invoiceKey), style: const TextStyle( fontFamily: 'monospace', fontSize: 12, @@ -206,11 +257,11 @@ class InvoiceKeyScreen extends StatelessWidget { ); } - Widget _buildCopyButton(BuildContext context, String inKey) { + Widget _buildCopyButton(BuildContext context, String invoiceKey) { return SizedBox( width: double.infinity, child: ElevatedButton.icon( - onPressed: () => _copyToClipboard(context, inKey), + onPressed: () => _copyToClipboard(context, invoiceKey), icon: const Icon(Icons.copy, size: 20), label: Text( AppLocalizations.of(context)!.copy_invoice_key, @@ -254,7 +305,7 @@ class InvoiceKeyScreen extends StatelessWidget { const SizedBox(width: 12), Expanded( child: Text( - AppLocalizations.of(context)!.invoice_key_qr_description, + AppLocalizations.of(context)!.invoice_key_security_warning, style: TextStyle( fontFamily: 'Inter', fontSize: 14, @@ -266,22 +317,4 @@ class InvoiceKeyScreen extends StatelessWidget { ), ); } - - void _copyToClipboard(BuildContext context, String text) { - Clipboard.setData(ClipboardData(text: text)); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context)!.invoice_key_copied, - style: const TextStyle(fontFamily: 'Inter'), - ), - backgroundColor: const Color(0xFF4C63F7), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - duration: const Duration(seconds: 2), - ), - ); - } } From 7b8a1ba05833d59a49389c041ae70e101c59bbb2 Mon Sep 17 00:00:00 2001 From: "@Delagado74" Date: Sun, 3 May 2026 05:12:12 -0400 Subject: [PATCH 3/3] feat: add localization keys for invoice key QR and currency count - Add invoice_key localization strings for all 7 languages - Add currency_count plural support for localized currency display - Regenerate localization files --- lib/l10n/app_de.arb | 9 +++ lib/l10n/app_en.arb | 9 +++ lib/l10n/app_es.arb | 9 +++ lib/l10n/app_fr.arb | 9 +++ lib/l10n/app_it.arb | 9 +++ lib/l10n/app_pt.arb | 9 +++ lib/l10n/app_ru.arb | 9 +++ lib/l10n/generated/app_localizations.dart | 78 ++++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 52 +++++++++++++ lib/l10n/generated/app_localizations_en.dart | 49 ++++++++++++ lib/l10n/generated/app_localizations_es.dart | 52 +++++++++++++ lib/l10n/generated/app_localizations_fr.dart | 53 +++++++++++++ lib/l10n/generated/app_localizations_it.dart | 49 ++++++++++++ lib/l10n/generated/app_localizations_pt.dart | 50 +++++++++++++ lib/l10n/generated/app_localizations_ru.dart | 49 ++++++++++++ 15 files changed, 495 insertions(+) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6f37a7c..74d0187 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -352,6 +352,15 @@ "voucher_generic_error": "Voucher kann nicht verarbeitet werden", "voucher_generic_error_desc": "Es gab einen unerwarteten Fehler beim Verarbeiten dieses Vouchers. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support.", + "currency_count": "{count, plural, =1{1 Währung} other{{count} Währungen}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "Bei der Auswahl einer Währung wird überprüft, ob sie auf diesem Server verfügbar ist", "checking_currency_availability": "Überprüfung der Verfügbarkeit von {currency}...", "currency_added_successfully": "{currency} erfolgreich hinzugefügt", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ee4edc3..153922d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -367,6 +367,15 @@ "settings_screen_title": "Settings", "about_title": "About", + "currency_count": "{count, plural, =1{1 currency} other{{count} currencies}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "When selecting a currency, it will be verified if it's available on this server", "checking_currency_availability": "Checking {currency} availability...", "currency_added_successfully": "{currency} added successfully", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b05c245..f2746ec 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -352,6 +352,15 @@ "settings_screen_title": "Configuración", "about_title": "Acerca de", + "currency_count": "{count, plural, =1{1 moneda} other{{count} monedas}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "Al seleccionar una moneda, se verificará si está disponible en este servidor", "checking_currency_availability": "Verificando disponibilidad de {currency}...", "currency_added_successfully": "{currency} agregado correctamente", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8e13028..e1501ec 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -352,6 +352,15 @@ "voucher_generic_error": "Impossible de traiter le voucher", "voucher_generic_error_desc": "Il y a eu une erreur inattendue lors du traitement de ce voucher. Veuillez réessayer ou contacter le support.", + "currency_count": "{count, plural, =1{1 monnaie} other{{count} monnaies}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "Lors de la sélection d'une devise, il sera vérifié si elle est disponible sur ce serveur", "checking_currency_availability": "Vérification de la disponibilité de {currency}...", "currency_added_successfully": "{currency} ajouté avec succès", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 8b660be..021f91b 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -352,6 +352,15 @@ "voucher_generic_error": "Impossibile elaborare il voucher", "voucher_generic_error_desc": "C'è stato un errore imprevisto nell'elaborazione di questo voucher. Riprova o contatta il supporto.", + "currency_count": "{count, plural, =1{1 valuta} other{{count} valute}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "Quando si seleziona una valuta, verrà verificato se è disponibile su questo server", "checking_currency_availability": "Verifica disponibilità di {currency}...", "currency_added_successfully": "{currency} aggiunto con successo", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index de5bbb8..0aa5dbd 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -352,6 +352,15 @@ "settings_screen_title": "Configurações", "about_title": "Sobre", + "currency_count": "{count, plural, =1{1 moeda} other{{count} moedas}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "Ao selecionar uma moeda, será verificado se está disponível neste servidor", "checking_currency_availability": "Verificando disponibilidade de {currency}...", "currency_added_successfully": "{currency} adicionado com sucesso", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index fc0810c..58101fd 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -352,6 +352,15 @@ "voucher_generic_error": "Не удается обработать ваучер", "voucher_generic_error_desc": "Произошла неожиданная ошибка при обработке этого ваучера. Попробуйте еще раз или обратитесь в поддержку.", + "currency_count": "{count, plural, =1{1 валюта} other{{count} валют}}", + "@currency_count": { + "description": "Number of currencies available", + "placeholders": { + "count": { + "type": "int" + } + } + }, "currency_validation_info": "При выборе валюты будет проверено, доступна ли она на этом сервере", "checking_currency_availability": "Проверка доступности {currency}...", "currency_added_successfully": "{currency} успешно добавлен", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index b7c379f..1342d14 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -1782,6 +1782,12 @@ abstract class AppLocalizations { /// **'Acerca de'** String get about_title; + /// Number of currencies available + /// + /// In es, this message translates to: + /// **'{count, plural, =1{1 moneda} other{{count} monedas}}'** + String currency_count(int count); + /// No description provided for @currency_validation_info. /// /// In es, this message translates to: @@ -1901,6 +1907,78 @@ abstract class AppLocalizations { /// In es, this message translates to: /// **'LNURL copiado al portapapeles'** String get lnurl_copied_message; + + /// No description provided for @invoice_key_qr_title. + /// + /// In es, this message translates to: + /// **'QR de Clave de Facturación'** + String get invoice_key_qr_title; + + /// No description provided for @invoice_key_qr_description. + /// + /// In es, this message translates to: + /// **'Usa este código QR con LaChispaPOS u otras apps Lightning para recibir pagos sin exponer tu clave de administrador.'** + String get invoice_key_qr_description; + + /// No description provided for @invoice_key_qr_subtitle. + /// + /// In es, this message translates to: + /// **'Mostrar QR para otras apps'** + String get invoice_key_qr_subtitle; + + /// No description provided for @copy_invoice_key. + /// + /// In es, this message translates to: + /// **'Copiar Clave'** + String get copy_invoice_key; + + /// No description provided for @invoice_key_copied. + /// + /// In es, this message translates to: + /// **'Clave de facturación copiada al portapapeles'** + String get invoice_key_copied; + + /// No description provided for @invoice_key_unavailable_title. + /// + /// In es, this message translates to: + /// **'No se encontró billetera'** + String get invoice_key_unavailable_title; + + /// No description provided for @invoice_key_unavailable_subtitle. + /// + /// In es, this message translates to: + /// **'Crea una billetera primero'** + String get invoice_key_unavailable_subtitle; + + /// No description provided for @invoice_key_security_warning. + /// + /// In es, this message translates to: + /// **'Esta clave permite a terceros crear facturas. Compártela solo con dispositivos POS de confianza. Nunca la publiques ni compartas públicamente.'** + String get invoice_key_security_warning; + + /// No description provided for @invoice_key_show. + /// + /// In es, this message translates to: + /// **'Mostrar clave'** + String get invoice_key_show; + + /// No description provided for @invoice_key_hide. + /// + /// In es, this message translates to: + /// **'Ocultar clave'** + String get invoice_key_hide; + + /// No description provided for @invoice_key_copy_failed. + /// + /// In es, this message translates to: + /// **'No se pudo copiar la clave de facturación'** + String get invoice_key_copy_failed; + + /// No description provided for @invoice_key_empty. + /// + /// In es, this message translates to: + /// **'La clave de facturación no puede estar vacía'** + String get invoice_key_empty; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 74bca7f..62ef1fd 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -927,6 +927,17 @@ class AppLocalizationsDe extends AppLocalizations { @override String get about_title => 'Über'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count Währungen', + one: '1 Währung', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'Bei der Auswahl einer Währung wird überprüft, ob sie auf diesem Server verfügbar ist'; @@ -998,4 +1009,45 @@ class AppLocalizationsDe extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL in die Zwischenablage kopiert'; + + @override + String get invoice_key_qr_title => 'Rechnungsschlüssel QR'; + + @override + String get invoice_key_qr_description => + 'Verwenden Sie diesen QR-Code mit LaChispaPOS oder anderen Lightning-Apps, um Zahlungen zu empfangen, ohne Ihren Admin-Schlüssel preiszugeben.'; + + @override + String get invoice_key_qr_subtitle => 'QR für andere Apps anzeigen'; + + @override + String get copy_invoice_key => 'Schlüssel kopieren'; + + @override + String get invoice_key_copied => + 'Rechnungsschlüssel in die Zwischenablage kopiert'; + + @override + String get invoice_key_unavailable_title => 'Keine Wallet gefunden'; + + @override + String get invoice_key_unavailable_subtitle => + 'Bitte erstellen Sie zuerst eine Wallet'; + + @override + String get invoice_key_security_warning => + 'Dieser Schlüssel ermöglicht Dritten, Rechnungen zu erstellen. Nur mit vertrauenswürdigen POS-Geräten teilen. Nie öffentlich posten oder teilen.'; + + @override + String get invoice_key_show => 'Schlüssel anzeigen'; + + @override + String get invoice_key_hide => 'Schlüssel verbergen'; + + @override + String get invoice_key_copy_failed => + 'Rechnungsschlüssel konnte nicht kopiert werden'; + + @override + String get invoice_key_empty => 'Rechnungsschlüssel darf nicht leer sein'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index c6eba38..7670114 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -903,6 +903,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get about_title => 'About'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count currencies', + one: '1 currency', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'When selecting a currency, it will be verified if it\'s available on this server'; @@ -974,4 +985,42 @@ class AppLocalizationsEn extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL copied to clipboard'; + + @override + String get invoice_key_qr_title => 'Invoice Key QR'; + + @override + String get invoice_key_qr_description => + 'Use this QR code with LaChispaPOS or other Lightning apps to receive payments without exposing your admin key.'; + + @override + String get invoice_key_qr_subtitle => 'Show QR for other apps'; + + @override + String get copy_invoice_key => 'Copy Key'; + + @override + String get invoice_key_copied => 'Invoice key copied to clipboard'; + + @override + String get invoice_key_unavailable_title => 'No wallet found'; + + @override + String get invoice_key_unavailable_subtitle => 'Create a wallet first'; + + @override + String get invoice_key_security_warning => + 'This key allows third parties to create invoices. Only share with trusted POS devices. Never post publicly or share widely.'; + + @override + String get invoice_key_show => 'Show key'; + + @override + String get invoice_key_hide => 'Hide key'; + + @override + String get invoice_key_copy_failed => 'Failed to copy invoice key'; + + @override + String get invoice_key_empty => 'Invoice key cannot be empty'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 3a882d8..98d0b9a 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -916,6 +916,17 @@ class AppLocalizationsEs extends AppLocalizations { @override String get about_title => 'Acerca de'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count monedas', + one: '1 moneda', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'Al seleccionar una moneda, se verificará si está disponible en este servidor'; @@ -987,4 +998,45 @@ class AppLocalizationsEs extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL copiado al portapapeles'; + + @override + String get invoice_key_qr_title => 'QR de Clave de Facturación'; + + @override + String get invoice_key_qr_description => + 'Usa este código QR con LaChispaPOS u otras apps Lightning para recibir pagos sin exponer tu clave de administrador.'; + + @override + String get invoice_key_qr_subtitle => 'Mostrar QR para otras apps'; + + @override + String get copy_invoice_key => 'Copiar Clave'; + + @override + String get invoice_key_copied => + 'Clave de facturación copiada al portapapeles'; + + @override + String get invoice_key_unavailable_title => 'No se encontró billetera'; + + @override + String get invoice_key_unavailable_subtitle => 'Crea una billetera primero'; + + @override + String get invoice_key_security_warning => + 'Esta clave permite a terceros crear facturas. Compártela solo con dispositivos POS de confianza. Nunca la publiques ni compartas públicamente.'; + + @override + String get invoice_key_show => 'Mostrar clave'; + + @override + String get invoice_key_hide => 'Ocultar clave'; + + @override + String get invoice_key_copy_failed => + 'No se pudo copiar la clave de facturación'; + + @override + String get invoice_key_empty => + 'La clave de facturación no puede estar vacía'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index eebd20a..98a620a 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -933,6 +933,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String get about_title => 'À propos'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count monnaies', + one: '1 monnaie', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'Lors de la sélection d\'une devise, il sera vérifié si elle est disponible sur ce serveur'; @@ -1005,4 +1016,46 @@ class AppLocalizationsFr extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL copié dans le presse-papiers'; + + @override + String get invoice_key_qr_title => 'QR de clé de facturation'; + + @override + String get invoice_key_qr_description => + 'Utilisez ce code QR avec LaChispaPOS ou d\'autres applications Lightning pour recevoir des paiements sans exposer votre clé administrateur.'; + + @override + String get invoice_key_qr_subtitle => + 'Afficher le QR pour d\'autres applications'; + + @override + String get copy_invoice_key => 'Copier la clé'; + + @override + String get invoice_key_copied => + 'Clé de facturation copiée dans le presse-papiers'; + + @override + String get invoice_key_unavailable_title => 'Aucun portefeuille trouvé'; + + @override + String get invoice_key_unavailable_subtitle => + 'Créez d\'abord un portefeuille'; + + @override + String get invoice_key_security_warning => + 'Cette clé permet à des tiers de créer des factures. Ne la partagez qu\'avec des appareils POS de confiance. Ne la publiez jamais publiquement.'; + + @override + String get invoice_key_show => 'Afficher la clé'; + + @override + String get invoice_key_hide => 'Masquer la clé'; + + @override + String get invoice_key_copy_failed => + 'Impossible de copier la clé de facturation'; + + @override + String get invoice_key_empty => 'La clé de facturation ne peut pas être vide'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 780cfaf..6ef6a7b 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -928,6 +928,17 @@ class AppLocalizationsIt extends AppLocalizations { @override String get about_title => 'Informazioni'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count valute', + one: '1 valuta', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'Quando si seleziona una valuta, verrà verificato se è disponibile su questo server'; @@ -1001,4 +1012,42 @@ class AppLocalizationsIt extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL copiato negli appunti'; + + @override + String get invoice_key_qr_title => 'QR Chiave fattura'; + + @override + String get invoice_key_qr_description => + 'Usa questo codice QR con LaChispaPOS o altre app Lightning per ricevere pagamenti senza esporre la tua chiave amministratore.'; + + @override + String get invoice_key_qr_subtitle => 'Mostra QR per altre app'; + + @override + String get copy_invoice_key => 'Copia chiave'; + + @override + String get invoice_key_copied => 'Chiave fattura copiata negli appunti'; + + @override + String get invoice_key_unavailable_title => 'Nessun portafoglio trovato'; + + @override + String get invoice_key_unavailable_subtitle => 'Crea prima un portafoglio'; + + @override + String get invoice_key_security_warning => + 'Questa chiave permette a terzi di creare fatture. Condividi solo con dispositivi POS fidati. Non pubblicare mai pubblicamente.'; + + @override + String get invoice_key_show => 'Mostra chiave'; + + @override + String get invoice_key_hide => 'Nascondi chiave'; + + @override + String get invoice_key_copy_failed => 'Impossibile copiare la chiave fattura'; + + @override + String get invoice_key_empty => 'La chiave fattura non può essere vuota'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 31cf0a5..be5b32f 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -915,6 +915,17 @@ class AppLocalizationsPt extends AppLocalizations { @override String get about_title => 'Sobre'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count moedas', + one: '1 moeda', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'Ao selecionar uma moeda, será verificado se está disponível neste servidor'; @@ -987,4 +998,43 @@ class AppLocalizationsPt extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL copiado para a área de transferência'; + + @override + String get invoice_key_qr_title => 'QR Chave da Fatura'; + + @override + String get invoice_key_qr_description => + 'Use este código QR com LaChispaPOS ou outros aplicativos Lightning para receber pagamentos sem expor sua chave de administrador.'; + + @override + String get invoice_key_qr_subtitle => 'Mostrar QR para outros aplicativos'; + + @override + String get copy_invoice_key => 'Copiar Chave'; + + @override + String get invoice_key_copied => + 'Chave da fatura copiada para a área de transferência'; + + @override + String get invoice_key_unavailable_title => 'Nenhuma carteira encontrada'; + + @override + String get invoice_key_unavailable_subtitle => 'Crie uma carteira primeiro'; + + @override + String get invoice_key_security_warning => + 'Esta chave permite que terceiros criem faturas. Compartilhe apenas com dispositivos POS confiáveis. Nunca publique publicamente.'; + + @override + String get invoice_key_show => 'Mostrar chave'; + + @override + String get invoice_key_hide => 'Ocultar chave'; + + @override + String get invoice_key_copy_failed => 'Falha ao copiar a chave da fatura'; + + @override + String get invoice_key_empty => 'A chave da fatura não pode estar vazia'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index c440253..cb5c31d 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -907,6 +907,17 @@ class AppLocalizationsRu extends AppLocalizations { @override String get about_title => 'Информация'; + @override + String currency_count(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count валют', + one: '1 валюта', + ); + return '$_temp0'; + } + @override String get currency_validation_info => 'При выборе валюты будет проверено, доступна ли она на этом сервере'; @@ -978,4 +989,42 @@ class AppLocalizationsRu extends AppLocalizations { @override String get lnurl_copied_message => 'LNURL скопирован в буфер обмена'; + + @override + String get invoice_key_qr_title => 'QR Ключ счета'; + + @override + String get invoice_key_qr_description => + 'Используйте этот QR-код с LaChispaPOS или другими приложениями Lightning для получения платежей без раскрытия ключа администратора.'; + + @override + String get invoice_key_qr_subtitle => 'Показать QR для других приложений'; + + @override + String get copy_invoice_key => 'Копировать ключ'; + + @override + String get invoice_key_copied => 'Ключ счета скопирован в буфер обмена'; + + @override + String get invoice_key_unavailable_title => 'Кошелек не найден'; + + @override + String get invoice_key_unavailable_subtitle => 'Сначала создайте кошелек'; + + @override + String get invoice_key_security_warning => + 'Этот ключ позволяет третьим лицам создавать счета. Делитесь только с доверенными POS-устройствами. Никогда не публикуйте публично.'; + + @override + String get invoice_key_show => 'Показать ключ'; + + @override + String get invoice_key_hide => 'Скрыть ключ'; + + @override + String get invoice_key_copy_failed => 'Не удалось скопировать ключ счета'; + + @override + String get invoice_key_empty => 'Ключ счета не может быть пустым'; }