diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ab39a10..43394ed 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b177357..0b95cbe 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -377,5 +377,9 @@ "share_ready_message": "Bereit zum Teilen", "lnurl_copied_message": "LNURL in die Zwischenablage kopiert", "qr_scanner_title": "QR scannen", - "qr_scanner_instructions": "Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen" + "qr_scanner_instructions": "Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen", + "qr_from_gallery_button": "Galerie", + "qr_decoding": "QR wird decodiert\u2026", + "qr_not_found_in_image": "Kein QR-Code im ausgewählten Bild gefunden", + "error_loading_image": "Fehler beim Laden des Bildes" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e5fc63f..49fe304 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -392,5 +392,9 @@ "share_ready_message": "Ready to share", "lnurl_copied_message": "LNURL copied to clipboard", "qr_scanner_title": "Scan QR", - "qr_scanner_instructions": "Point the camera at the QR code\nto scan the invoice or address" + "qr_scanner_instructions": "Point the camera at the QR code\nto scan the invoice or address", + "qr_from_gallery_button": "Gallery", + "qr_decoding": "Decoding QR\u2026", + "qr_not_found_in_image": "No QR code found in the selected image", + "error_loading_image": "Error loading image" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9abb5fa..858b087 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -377,5 +377,9 @@ "share_ready_message": "Listo para compartir", "lnurl_copied_message": "LNURL copiado al portapapeles", "qr_scanner_title": "Escanear QR", - "qr_scanner_instructions": "Apunta la cámara al código QR\npara escanear la factura o dirección" + "qr_scanner_instructions": "Apunta la cámara al código QR\npara escanear la factura o dirección", + "qr_from_gallery_button": "Galería", + "qr_decoding": "Decodificando QR\u2026", + "qr_not_found_in_image": "No se encontró ningún código QR en la imagen seleccionada", + "error_loading_image": "Error al cargar la imagen" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7e07862..1aae85a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -377,5 +377,9 @@ "share_ready_message": "Prêt à partager", "lnurl_copied_message": "LNURL copié dans le presse-papiers", "qr_scanner_title": "Scanner QR", - "qr_scanner_instructions": "Pointez la caméra vers le code QR\npour scanner la facture ou l'adresse" + "qr_scanner_instructions": "Pointez la caméra vers le code QR\npour scanner la facture ou l'adresse", + "qr_from_gallery_button": "Galerie", + "qr_decoding": "Décodage du QR\u2026", + "qr_not_found_in_image": "Aucun code QR trouvé dans l'image sélectionnée", + "error_loading_image": "Erreur lors du chargement de l'image" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 0e62f78..e5538d0 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -377,5 +377,9 @@ "share_ready_message": "Pronto da condividere", "lnurl_copied_message": "LNURL copiato negli appunti", "qr_scanner_title": "Scansiona QR", - "qr_scanner_instructions": "Punta la fotocamera sul codice QR\nper scansionare la fattura o l'indirizzo" + "qr_scanner_instructions": "Punta la fotocamera sul codice QR\nper scansionare la fattura o l'indirizzo", + "qr_from_gallery_button": "Galleria", + "qr_decoding": "Decodifica QR in corso\u2026", + "qr_not_found_in_image": "Nessun codice QR trovato nell'immagine selezionata", + "error_loading_image": "Errore durante il caricamento dell'immagine" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 5a3e9a5..d3eca21 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -377,5 +377,9 @@ "share_ready_message": "Pronto para compartilhar", "lnurl_copied_message": "LNURL copiado para a área de transferência", "qr_scanner_title": "Escanear QR", - "qr_scanner_instructions": "Aponte a câmera para o código QR\npara escanear a fatura ou endereço" + "qr_scanner_instructions": "Aponte a câmera para o código QR\npara escanear a fatura ou endereço", + "qr_from_gallery_button": "Galeria", + "qr_decoding": "Decodificando QR\u2026", + "qr_not_found_in_image": "Nenhum código QR encontrado na imagem selecionada", + "error_loading_image": "Erro ao carregar a imagem" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 8076ab3..7555e20 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -377,5 +377,9 @@ "share_ready_message": "Готово к отправке", "lnurl_copied_message": "LNURL скопирован в буфер обмена", "qr_scanner_title": "Сканировать QR", - "qr_scanner_instructions": "Наведите камеру на QR-код\nчтобы сканировать счёт или адрес" + "qr_scanner_instructions": "Наведите камеру на QR-код\nчтобы сканировать счёт или адрес", + "qr_from_gallery_button": "Галерея", + "qr_decoding": "Декодирование QR\u2026", + "qr_not_found_in_image": "QR-код не найден в выбранном изображении", + "error_loading_image": "Ошибка загрузки изображения" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 49457e1..86e0a03 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -1925,6 +1925,30 @@ abstract class AppLocalizations { /// In es, this message translates to: /// **'Apunta la cámara al código QR\npara escanear la factura o dirección'** String get qr_scanner_instructions; + + /// No description provided for @qr_from_gallery_button. + /// + /// In es, this message translates to: + /// **'Galería'** + String get qr_from_gallery_button; + + /// No description provided for @qr_decoding. + /// + /// In es, this message translates to: + /// **'Decodificando QR…'** + String get qr_decoding; + + /// No description provided for @qr_not_found_in_image. + /// + /// In es, this message translates to: + /// **'No se encontró ningún código QR en la imagen seleccionada'** + String get qr_not_found_in_image; + + /// No description provided for @error_loading_image. + /// + /// In es, this message translates to: + /// **'Error al cargar la imagen'** + String get error_loading_image; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 031d657..a2f2363 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1014,4 +1014,17 @@ class AppLocalizationsDe extends AppLocalizations { @override String get qr_scanner_instructions => 'Kamera auf den QR-Code richten\num Rechnung oder Adresse zu scannen'; + + @override + String get qr_from_gallery_button => 'Galerie'; + + @override + String get qr_decoding => 'QR wird decodiert…'; + + @override + String get qr_not_found_in_image => + 'Kein QR-Code im ausgewählten Bild gefunden'; + + @override + String get error_loading_image => 'Fehler beim Laden des Bildes'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 3b87bf7..fc9f767 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -989,4 +989,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get qr_scanner_instructions => 'Point the camera at the QR code\nto scan the invoice or address'; + + @override + String get qr_from_gallery_button => 'Gallery'; + + @override + String get qr_decoding => 'Decoding QR…'; + + @override + String get qr_not_found_in_image => 'No QR code found in the selected image'; + + @override + String get error_loading_image => 'Error loading image'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index d28b1d1..fd0b0fd 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1003,4 +1003,17 @@ class AppLocalizationsEs extends AppLocalizations { @override String get qr_scanner_instructions => 'Apunta la cámara al código QR\npara escanear la factura o dirección'; + + @override + String get qr_from_gallery_button => 'Galería'; + + @override + String get qr_decoding => 'Decodificando QR…'; + + @override + String get qr_not_found_in_image => + 'No se encontró ningún código QR en la imagen seleccionada'; + + @override + String get error_loading_image => 'Error al cargar la imagen'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index c1c2d0a..b699a30 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1020,4 +1020,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String get qr_scanner_instructions => 'Pointez la caméra vers le code QR\npour scanner la facture ou l\'adresse'; + + @override + String get qr_from_gallery_button => 'Galerie'; + + @override + String get qr_decoding => 'Décodage du QR…'; + + @override + String get qr_not_found_in_image => + 'Aucun code QR trouvé dans l\'image sélectionnée'; + + @override + String get error_loading_image => 'Erreur lors du chargement de l\'image'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 6021611..f2caf88 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1017,4 +1017,18 @@ class AppLocalizationsIt extends AppLocalizations { @override String get qr_scanner_instructions => 'Punta la fotocamera sul codice QR\nper scansionare la fattura o l\'indirizzo'; + + @override + String get qr_from_gallery_button => 'Galleria'; + + @override + String get qr_decoding => 'Decodifica QR in corso…'; + + @override + String get qr_not_found_in_image => + 'Nessun codice QR trovato nell\'immagine selezionata'; + + @override + String get error_loading_image => + 'Errore durante il caricamento dell\'immagine'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 23cfd53..f9c9132 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1003,4 +1003,17 @@ class AppLocalizationsPt extends AppLocalizations { @override String get qr_scanner_instructions => 'Aponte a câmera para o código QR\npara escanear a fatura ou endereço'; + + @override + String get qr_from_gallery_button => 'Galeria'; + + @override + String get qr_decoding => 'Decodificando QR…'; + + @override + String get qr_not_found_in_image => + 'Nenhum código QR encontrado na imagem selecionada'; + + @override + String get error_loading_image => 'Erro ao carregar a imagem'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index aa87202..f8ad3e5 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -993,4 +993,17 @@ class AppLocalizationsRu extends AppLocalizations { @override String get qr_scanner_instructions => 'Наведите камеру на QR-код\nчтобы сканировать счёт или адрес'; + + @override + String get qr_from_gallery_button => 'Галерея'; + + @override + String get qr_decoding => 'Декодирование QR…'; + + @override + String get qr_not_found_in_image => + 'QR-код не найден в выбранном изображении'; + + @override + String get error_loading_image => 'Ошибка загрузки изображения'; } diff --git a/lib/widgets/qr_scanner_widget.dart b/lib/widgets/qr_scanner_widget.dart index 390f161..5d0deed 100644 --- a/lib/widgets/qr_scanner_widget.dart +++ b/lib/widgets/qr_scanner_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../l10n/generated/app_localizations.dart'; import '../theme/app_tokens.dart'; @@ -178,6 +179,62 @@ class _QRScannerWidgetState extends State { } } + Future _pickFromGallery() async { + try { + final picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: ImageSource.gallery, + maxWidth: 1024, + maxHeight: 1024, + ); + + if (image == null) return; + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.qr_decoding, + ), + duration: const Duration(seconds: 1), + ), + ); + + final BarcodeCapture? result = await controller.analyzeImage( + image.path, + ); + + if (result != null && + result.barcodes.isNotEmpty && + result.barcodes.first.rawValue != null) { + hasScanned = true; + controller.stop(); + if (!mounted) return; + widget.onScanned(result.barcodes.first.rawValue!); + } else { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.qr_not_found_in_image, + ), + ), + ); + } + } catch (e) { + debugPrint('Error loading image from gallery: $e'); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.error_loading_image, + ), + ), + ); + } + } + @override Widget build(BuildContext context) { final t = context.tokens; @@ -341,6 +398,29 @@ class _QRScannerWidgetState extends State { ), ), + // Pick from gallery button + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: t.textPrimary.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: t.textPrimary.withValues(alpha: 0.2), + width: 1, + ), + ), + child: IconButton( + icon: Icon( + Icons.photo_library_outlined, + color: t.textPrimary, + size: 24, + ), + onPressed: _pickFromGallery, + tooltip: AppLocalizations.of(context)!.qr_from_gallery_button, + ), + ), + // Switch camera button Container( width: 56, diff --git a/pubspec.lock b/pubspec.lock index 5fad4f4..5628c49 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" fixnum: dependency: transitive description: @@ -203,6 +235,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" flutter_secure_storage: dependency: "direct main" description: @@ -293,6 +333,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f + url: "https://pub.dev" + source: hosted + version: "0.8.13+17" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" + source: hosted + version: "0.8.13+6" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" intl: dependency: "direct main" description: @@ -827,5 +931,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.0-0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.11.5 <4.0.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 46cf793..b2cc235 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: shared_preferences: ^2.0.0 dio: ^5.4.0 flutter_secure_storage: ^9.0.0 + image_picker: 1.1.1 path: ^1.8.3 crypto: ^3.0.3 qr_flutter: ^4.1.0