From 57bb288bcc724eeed6fe1963f74eb69174b7ac5b Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Tue, 13 Jan 2026 13:10:54 +0100 Subject: [PATCH] qr scanner initial --- .../presentation/widgets/search_field.dart | 21 ++++- .../dialogs/qr_scanner_dialog.dart | 77 +++++++++++++++++++ .../widgets/qr_scanner_button.dart | 28 +++++++ app/pubspec.yaml | 1 + 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 app/lib/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart create mode 100644 app/lib/presentation/widgets/qr_scanner_button.dart diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart index f9394d99..1edf0db7 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart @@ -25,6 +25,7 @@ import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/engine_suggestions.dart'; import 'package:weblibre/features/user/domain/providers.dart'; import 'package:weblibre/presentation/widgets/auto_suggest_text_field.dart'; +import 'package:weblibre/presentation/widgets/qr_scanner_button.dart'; import 'package:weblibre/presentation/widgets/speech_to_text_button.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; @@ -122,10 +123,22 @@ class SearchField extends HookConsumerWidget { }, icon: const Icon(Icons.clear), ) - : SpeechToTextButton( - onTextReceived: (data) { - textEditingController.text = data.toString(); - }, + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + QrScannerButton( + onScanResult: (scanResult) { + if (scanResult?.code != null) { + textEditingController.text = scanResult!.code!; + } + }, + ), + SpeechToTextButton( + onTextReceived: (data) { + textEditingController.text = data; + }, + ), + ], ), ), onTapOutside: unfocusOnTapOutside diff --git a/app/lib/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart b/app/lib/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart new file mode 100644 index 00000000..e54dcb1a --- /dev/null +++ b/app/lib/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart @@ -0,0 +1,77 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; +import 'package:weblibre/utils/ui_helper.dart' as ui_helper; + +class QrScannerDialog extends HookWidget { + @override + Widget build(BuildContext context) { + final qrKey = useMemoized(() => GlobalKey(debugLabel: 'qr_scanner')); + final qrController = useState(null); + + final flashState = useState(false); + + useEffect(() { + StreamSubscription? sub; + + if (qrController.value != null) { + sub = qrController.value!.scannedDataStream.listen((event) { + if (context.mounted) { + Navigator.pop(context, event); + } + }); + } + + return () { + unawaited(sub?.cancel()); + }; + }, [qrController.value]); + + return Dialog.fullscreen( + child: Scaffold( + appBar: AppBar( + title: const Text('Scan code'), + actions: [ + IconButton( + isSelected: flashState.value, + onPressed: () async { + if (qrController.value != null) { + await qrController.value!.toggleFlash(); + + flashState.value = + await qrController.value!.getFlashStatus() ?? false; + } + }, + icon: const Icon(Icons.flash_on), + selectedIcon: const Icon(Icons.flash_off), + ), + IconButton( + onPressed: () async { + await qrController.value?.flipCamera(); + }, + icon: const Icon(Icons.cameraswitch), + ), + ], + ), + body: SafeArea( + child: QRView( + key: qrKey, + onQRViewCreated: (controller) { + qrController.value = controller; + }, + onPermissionSet: (controller, result) { + if (!result) { + ui_helper.showErrorMessage( + context, + 'No Camera Permission granted', + ); + } + }, + ), + ), + ), + ); + } +} diff --git a/app/lib/presentation/widgets/qr_scanner_button.dart b/app/lib/presentation/widgets/qr_scanner_button.dart new file mode 100644 index 00000000..42d39c99 --- /dev/null +++ b/app/lib/presentation/widgets/qr_scanner_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; +import 'package:weblibre/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart'; + +class QrScannerButton extends HookConsumerWidget { + final void Function(Barcode? scanResult) onScanResult; + + const QrScannerButton({required this.onScanResult}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return IconButton( + onPressed: () async { + final result = await showDialog( + context: context, + builder: (context) { + return QrScannerDialog(); + }, + ); + + onScanResult(result); + }, + icon: const Icon(MdiIcons.barcodeScan), + ); + } +} diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 118a4d22..9fb95a8a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: path_provider: ^2.1.5 permission_handler: ^12.0.1 pretty_qr_code: ^3.5.0 + qr_code_scanner_plus: ^2.0.14 quick_actions: ^1.1.0 riverpod: ^3.1.0 riverpod_annotation: ^4.0.0