- Added null checks in `reassemble` method to avoid potential null pointer exceptions when pausing/resuming the camera. - Refactored image picking and QR code decoding process to use async/await, avoiding UI blocking with synchronous file reads. - Improved exception handling by making it more specific to QR code reading errors. - Introduced `StreamSubscription` for the QR scan listener and ensured proper cancellation in `dispose` method to prevent memory leaks. - Separated button building logic (`_buildImagePickerButton`, `_buildFlashToggleButton`, `_buildCameraSwitchButton`) to enhance code readability and maintainability.
		
			
				
	
	
		
			166 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| import 'dart:io';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:image/image.dart' as img;
 | |
| import 'package:image_picker/image_picker.dart';
 | |
| import 'package:qr_code_scanner/qr_code_scanner.dart';
 | |
| import 'package:zxing2/qrcode.dart';
 | |
| 
 | |
| import '../../common.dart';
 | |
| import '../../models/platform_model.dart';
 | |
| import '../widgets/dialog.dart';
 | |
| 
 | |
| class ScanPage extends StatefulWidget {
 | |
|   @override
 | |
|   State<ScanPage> createState() => _ScanPageState();
 | |
| }
 | |
| 
 | |
| class _ScanPageState extends State<ScanPage> {
 | |
|   QRViewController? controller;
 | |
|   final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
 | |
|   StreamSubscription? scanSubscription;
 | |
| 
 | |
|   @override
 | |
|   void reassemble() {
 | |
|     super.reassemble();
 | |
|     if (isAndroid && controller != null) {
 | |
|       controller!.pauseCamera();
 | |
|     } else if (controller != null) {
 | |
|       controller!.resumeCamera();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Scaffold(
 | |
|       appBar: AppBar(
 | |
|         title: const Text('Scan QR'),
 | |
|         actions: [
 | |
|           _buildImagePickerButton(),
 | |
|           _buildFlashToggleButton(),
 | |
|           _buildCameraSwitchButton(),
 | |
|         ],
 | |
|       ),
 | |
|       body: _buildQrView(context),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildQrView(BuildContext context) {
 | |
|     var scanArea = MediaQuery.of(context).size.width < 400 ||
 | |
|             MediaQuery.of(context).size.height < 400
 | |
|         ? 150.0
 | |
|         : 300.0;
 | |
|     return QRView(
 | |
|       key: qrKey,
 | |
|       onQRViewCreated: _onQRViewCreated,
 | |
|       overlay: QrScannerOverlayShape(
 | |
|         borderColor: Colors.red,
 | |
|         borderRadius: 10,
 | |
|         borderLength: 30,
 | |
|         borderWidth: 10,
 | |
|         cutOutSize: scanArea,
 | |
|       ),
 | |
|       onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   void _onQRViewCreated(QRViewController controller) {
 | |
|     setState(() {
 | |
|       this.controller = controller;
 | |
|     });
 | |
|     scanSubscription = controller.scannedDataStream.listen((scanData) {
 | |
|       if (scanData.code != null) {
 | |
|         showServerSettingFromQr(scanData.code!);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
 | |
|     if (!p) {
 | |
|       showToast('No permission');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<void> _pickImage() async {
 | |
|     final ImagePicker picker = ImagePicker();
 | |
|     final XFile? file = await picker.pickImage(source: ImageSource.gallery);
 | |
|     if (file != null) {
 | |
|       try {
 | |
|         var image = img.decodeImage(await File(file.path).readAsBytes())!;
 | |
|         LuminanceSource source = RGBLuminanceSource(
 | |
|           image.width,
 | |
|           image.height,
 | |
|           image.getBytes(order: img.ChannelOrder.abgr).buffer.asInt32List(),
 | |
|         );
 | |
|         var bitmap = BinaryBitmap(HybridBinarizer(source));
 | |
| 
 | |
|         var reader = QRCodeReader();
 | |
|         var result = reader.decode(bitmap);
 | |
|         if (result.text.startsWith(bind.mainUriPrefixSync())) {
 | |
|           handleUriLink(uriString: result.text);
 | |
|         } else {
 | |
|           showServerSettingFromQr(result.text);
 | |
|         }
 | |
|       } catch (e) {
 | |
|         showToast('No QR code found');
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Widget _buildImagePickerButton() {
 | |
|     return IconButton(
 | |
|       color: Colors.white,
 | |
|       icon: Icon(Icons.image_search),
 | |
|       iconSize: 32.0,
 | |
|       onPressed: _pickImage,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildFlashToggleButton() {
 | |
|     return IconButton(
 | |
|       color: Colors.yellow,
 | |
|       icon: Icon(Icons.flash_on),
 | |
|       iconSize: 32.0,
 | |
|       onPressed: () async {
 | |
|         await controller?.toggleFlash();
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildCameraSwitchButton() {
 | |
|     return IconButton(
 | |
|       color: Colors.white,
 | |
|       icon: Icon(Icons.switch_camera),
 | |
|       iconSize: 32.0,
 | |
|       onPressed: () async {
 | |
|         await controller?.flipCamera();
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     scanSubscription?.cancel();
 | |
|     controller?.dispose();
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   void showServerSettingFromQr(String data) async {
 | |
|     closeConnection();
 | |
|     await controller?.pauseCamera();
 | |
|     if (!data.startsWith('config=')) {
 | |
|       showToast('Invalid QR code');
 | |
|       return;
 | |
|     }
 | |
|     try {
 | |
|       final sc = ServerConfig.decode(data.substring(7));
 | |
|       Timer(Duration(milliseconds: 60), () {
 | |
|         showServerSettingsWithValue(sc, gFFI.dialogManager);
 | |
|       });
 | |
|     } catch (e) {
 | |
|       showToast('Invalid QR code');
 | |
|     }
 | |
|   }
 | |
| }
 |