From 3437b670ea067e123e29cf335551ccbff51e1ffa Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 23 Jun 2025 21:32:17 -0700 Subject: [PATCH] Add QR scanner --- android/app/src/main/AndroidManifest.xml | 1 + ios/Podfile.lock | 7 ++ ios/Runner/Info.plist | 2 + lib/qr_code_screen.dart | 89 ++++++++++++++++++++++++ lib/settings_screen.dart | 43 +++++------- pubspec.lock | 10 ++- pubspec.yaml | 1 + 7 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 lib/qr_code_screen.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b5a19b1..eb2c75d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + Location required when app is in use NSMotionUsageDescription Motion permission helps detect when device in in-motion + NSCameraUsageDescription + Camera access is required to scan QR codes UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/lib/qr_code_screen.dart b/lib/qr_code_screen.dart new file mode 100644 index 0000000..9ddf2ee --- /dev/null +++ b/lib/qr_code_screen.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; + +import 'preferences.dart'; +import 'l10n/app_localizations.dart'; + +class QrCodeScreen extends StatefulWidget { + const QrCodeScreen({super.key}); + + @override + State createState() => _QrCodeScreenState(); +} + +class _QrCodeScreenState extends State { + bool _scanned = false; + + Future _applySettings(Uri uri) async { + await Preferences.instance.setString(Preferences.url, '${uri.origin}${uri.path}'); + final parameters = uri.queryParameters; + await _applyStringParameter(parameters, Preferences.id); + await _applyStringParameter(parameters, Preferences.accuracy); + await _applyIntParameter(parameters, Preferences.distance); + await _applyIntParameter(parameters, Preferences.interval); + await _applyIntParameter(parameters, Preferences.angle); + await _applyIntParameter(parameters, Preferences.heartbeat); + await _applyIntParameter(parameters, Preferences.fastestInterval); + await _applyBoolParameter(parameters, Preferences.buffer); + await _applyBoolParameter(parameters, Preferences.wakelock); + await _applyBoolParameter(parameters, Preferences.stopDetection); + await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); + } + + Future _applyStringParameter(Map parameters, String key) async { + final value = parameters[key]; + if (value != null) { + await Preferences.instance.setString(key, value); + } + } + + Future _applyIntParameter(Map parameters, String key) async { + final stringValue = parameters[key]; + if (stringValue != null) { + final value = int.tryParse(stringValue); + if (value != null) { + await Preferences.instance.setInt(key, value); + } + } + } + + Future _applyBoolParameter(Map parameters, String key) async { + final value = parameters[key]; + if (value != null) { + switch (value) { + case 'false': + await Preferences.instance.setBool(key, false); + break; + case 'true': + await Preferences.instance.setBool(key, true); + break; + } + } + } + + void _onDetect(BarcodeCapture capture) async { + if (_scanned) return; + final barcode = capture.barcodes.first; + final rawValue = barcode.rawValue; + if (rawValue == null) return; + final uri = Uri.tryParse(rawValue); + if (uri == null || uri.scheme.isEmpty) return; + _scanned = true; + await _applySettings(uri); + if (mounted) Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.settingsTitle), + ), + body: MobileScanner( + fit: BoxFit.cover, + onDetect: _onDetect, + ), + ); + } +} diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index f8d22c7..8376c01 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; +import 'package:traccar_client/qr_code_screen.dart'; import 'package:wakelock_partial_android/wakelock_partial_android.dart'; import 'l10n/app_localizations.dart'; @@ -17,23 +18,6 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { bool advanced = false; - bool buffering = true; - bool wakelock = false; - bool stopDetection = true; - - @override - void initState() { - super.initState(); - _initState(); - } - - void _initState() async { - setState(() { - buffering = Preferences.instance.getBool(Preferences.buffer) ?? true; - wakelock = Preferences.instance.getBool(Preferences.wakelock) ?? false; - stopDetection = Preferences.instance.getBool(Preferences.stopDetection) ?? true; - }); - } String _getAccuracyLabel(String? key) { return switch (key) { @@ -148,7 +132,18 @@ class _SettingsScreenState extends State { final isHighestAccuracy = Preferences.instance.getString(Preferences.accuracy) == 'highest'; final distance = Preferences.instance.getInt(Preferences.distance); return Scaffold( - appBar: AppBar(title: Text(AppLocalizations.of(context)!.settingsTitle)), + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.settingsTitle), + actions: [ + IconButton( + icon: const Icon(Icons.qr_code_scanner), + onPressed: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const QrCodeScreen())); + setState(() {}); + }, + ), + ], + ), body: ListView( children: [ _buildListTile(AppLocalizations.of(context)!.idLabel, Preferences.id, false), @@ -172,17 +167,17 @@ class _SettingsScreenState extends State { if (advanced) SwitchListTile( title: Text(AppLocalizations.of(context)!.bufferLabel), - value: buffering, + value: Preferences.instance.getBool(Preferences.buffer) ?? true, onChanged: (value) async { await Preferences.instance.setBool(Preferences.buffer, value); await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() => buffering = value); + setState(() {}); }, ), if (advanced && Platform.isAndroid) SwitchListTile( title: Text(AppLocalizations.of(context)!.wakelockLabel), - value: wakelock, + value: Preferences.instance.getBool(Preferences.wakelock) ?? false, onChanged: (value) async { await Preferences.instance.setBool(Preferences.wakelock, value); if (value) { @@ -193,17 +188,17 @@ class _SettingsScreenState extends State { } else { WakelockPartialAndroid.release(); } - setState(() => wakelock = value); + setState(() {}); }, ), if (advanced) SwitchListTile( title: Text(AppLocalizations.of(context)!.stopDetectionLabel), - value: stopDetection, + value: Preferences.instance.getBool(Preferences.stopDetection) ?? true, onChanged: (value) async { await Preferences.instance.setBool(Preferences.stopDetection, value); await bg.BackgroundGeolocation.setConfig(Preferences.geolocationConfig()); - setState(() => stopDetection = value); + setState(() {}); }, ), ], diff --git a/pubspec.lock b/pubspec.lock index b025785..54d133a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -277,6 +277,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95" + url: "https://pub.dev" + source: hosted + version: "7.0.1" path: dependency: transitive description: @@ -516,4 +524,4 @@ packages: version: "1.1.0" sdks: dart: ">=3.8.1 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 53582d9..29ebe7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: rate_my_app: ^2.3.1 shared_preferences_android: ^2.4.10 wakelock_partial_android: ^1.0.0 + mobile_scanner: ^7.0.1 dev_dependencies: flutter_test: