294 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| import 'dart:convert';
 | |
| 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';
 | |
| 
 | |
| class ScanPage extends StatefulWidget {
 | |
|   @override
 | |
|   _ScanPageState createState() => _ScanPageState();
 | |
| }
 | |
| 
 | |
| class _ScanPageState extends State<ScanPage> {
 | |
|   QRViewController? controller;
 | |
|   final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
 | |
| 
 | |
|   // In order to get hot reload to work we need to pause the camera if the platform
 | |
|   // is android, or resume the camera if the platform is iOS.
 | |
|   @override
 | |
|   void reassemble() {
 | |
|     super.reassemble();
 | |
|     if (isAndroid) {
 | |
|       controller!.pauseCamera();
 | |
|     }
 | |
|     controller!.resumeCamera();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Scaffold(
 | |
|         appBar: AppBar(
 | |
|           title: const Text('Scan QR'),
 | |
|           actions: [
 | |
|             IconButton(
 | |
|                 color: Colors.white,
 | |
|                 icon: Icon(Icons.image_search),
 | |
|                 iconSize: 32.0,
 | |
|                 onPressed: () async {
 | |
|                   final ImagePicker _picker = ImagePicker();
 | |
|                   final XFile? file =
 | |
|                       await _picker.pickImage(source: ImageSource.gallery);
 | |
|                   if (file != null) {
 | |
|                     var image = img.decodeNamedImage(
 | |
|                         File(file.path).readAsBytesSync(), file.path)!;
 | |
| 
 | |
|                     LuminanceSource source = RGBLuminanceSource(
 | |
|                         image.width,
 | |
|                         image.height,
 | |
|                         image
 | |
|                             .getBytes(format: img.Format.abgr)
 | |
|                             .buffer
 | |
|                             .asInt32List());
 | |
|                     var bitmap = BinaryBitmap(HybridBinarizer(source));
 | |
| 
 | |
|                     var reader = QRCodeReader();
 | |
|                     try {
 | |
|                       var result = reader.decode(bitmap);
 | |
|                       showServerSettingFromQr(result.text);
 | |
|                     } catch (e) {
 | |
|                       showToast('No QR code found');
 | |
|                     }
 | |
|                   }
 | |
|                 }),
 | |
|             IconButton(
 | |
|                 color: Colors.yellow,
 | |
|                 icon: Icon(Icons.flash_on),
 | |
|                 iconSize: 32.0,
 | |
|                 onPressed: () async {
 | |
|                   await controller?.toggleFlash();
 | |
|                 }),
 | |
|             IconButton(
 | |
|               color: Colors.white,
 | |
|               icon: Icon(Icons.switch_camera),
 | |
|               iconSize: 32.0,
 | |
|               onPressed: () async {
 | |
|                 await controller?.flipCamera();
 | |
|               },
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|         body: _buildQrView(context));
 | |
|   }
 | |
| 
 | |
|   Widget _buildQrView(BuildContext context) {
 | |
|     // For this example we check how width or tall the device is and change the scanArea and overlay accordingly.
 | |
|     var scanArea = (MediaQuery.of(context).size.width < 400 ||
 | |
|             MediaQuery.of(context).size.height < 400)
 | |
|         ? 150.0
 | |
|         : 300.0;
 | |
|     // To ensure the Scanner view is properly sizes after rotation
 | |
|     // we need to listen for Flutter SizeChanged notification and update controller
 | |
|     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;
 | |
|     });
 | |
|     controller.scannedDataStream.listen((scanData) {
 | |
|       if (scanData.code != null) {
 | |
|         showServerSettingFromQr(scanData.code!);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
 | |
|     if (!p) {
 | |
|       showToast('No permission');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     controller?.dispose();
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   void showServerSettingFromQr(String data) async {
 | |
|     closeConnection();
 | |
|     await controller?.pauseCamera();
 | |
|     if (!data.startsWith('config=')) {
 | |
|       showToast('Invalid QR code');
 | |
|       return;
 | |
|     }
 | |
|     try {
 | |
|       Map<String, dynamic> values = json.decode(data.substring(7));
 | |
|       var host = values['host'] != null ? values['host'] as String : '';
 | |
|       var key = values['key'] != null ? values['key'] as String : '';
 | |
|       var api = values['api'] != null ? values['api'] as String : '';
 | |
|       Timer(Duration(milliseconds: 60), () {
 | |
|         showServerSettingsWithValue(host, '', key, api, gFFI.dialogManager);
 | |
|       });
 | |
|     } catch (e) {
 | |
|       showToast('Invalid QR code');
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void showServerSettingsWithValue(String id, String relay, String key,
 | |
|     String api, OverlayDialogManager dialogManager) async {
 | |
|   Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
 | |
|   String id0 = oldOptions['custom-rendezvous-server'] ?? "";
 | |
|   String relay0 = oldOptions['relay-server'] ?? "";
 | |
|   String api0 = oldOptions['api-server'] ?? "";
 | |
|   String key0 = oldOptions['key'] ?? "";
 | |
|   var isInProgress = false;
 | |
|   final idController = TextEditingController(text: id);
 | |
|   final relayController = TextEditingController(text: relay);
 | |
|   final apiController = TextEditingController(text: api);
 | |
| 
 | |
|   String? idServerMsg;
 | |
|   String? relayServerMsg;
 | |
|   String? apiServerMsg;
 | |
| 
 | |
|   dialogManager.show((setState, close) {
 | |
|     Future<bool> validate() async {
 | |
|       if (idController.text != id) {
 | |
|         final res = await validateAsync(idController.text);
 | |
|         setState(() => idServerMsg = res);
 | |
|         if (idServerMsg != null) return false;
 | |
|         id = idController.text;
 | |
|       }
 | |
|       if (relayController.text != relay) {
 | |
|         relayServerMsg = await validateAsync(relayController.text);
 | |
|         if (relayServerMsg != null) return false;
 | |
|         relay = relayController.text;
 | |
|       }
 | |
|       if (apiController.text != relay) {
 | |
|         apiServerMsg = await validateAsync(apiController.text);
 | |
|         if (apiServerMsg != null) return false;
 | |
|         api = apiController.text;
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     return CustomAlertDialog(
 | |
|       title: Text(translate('ID/Relay Server')),
 | |
|       content: Form(
 | |
|           child: Column(
 | |
|               mainAxisSize: MainAxisSize.min,
 | |
|               children: <Widget>[
 | |
|                     TextFormField(
 | |
|                       controller: idController,
 | |
|                       decoration: InputDecoration(
 | |
|                           labelText: translate('ID Server'),
 | |
|                           errorText: idServerMsg),
 | |
|                     )
 | |
|                   ] +
 | |
|                   (isAndroid
 | |
|                       ? [
 | |
|                           TextFormField(
 | |
|                             controller: relayController,
 | |
|                             decoration: InputDecoration(
 | |
|                                 labelText: translate('Relay Server'),
 | |
|                                 errorText: relayServerMsg),
 | |
|                           )
 | |
|                         ]
 | |
|                       : []) +
 | |
|                   [
 | |
|                     TextFormField(
 | |
|                       controller: apiController,
 | |
|                       decoration: InputDecoration(
 | |
|                         labelText: translate('API Server'),
 | |
|                       ),
 | |
|                       autovalidateMode: AutovalidateMode.onUserInteraction,
 | |
|                       validator: (v) {
 | |
|                         if (v != null && v.length > 0) {
 | |
|                           if (!(v.startsWith('http://') ||
 | |
|                               v.startsWith("https://"))) {
 | |
|                             return translate("invalid_http");
 | |
|                           }
 | |
|                         }
 | |
|                         return apiServerMsg;
 | |
|                       },
 | |
|                     ),
 | |
|                     TextFormField(
 | |
|                       initialValue: key,
 | |
|                       decoration: InputDecoration(
 | |
|                         labelText: 'Key',
 | |
|                       ),
 | |
|                       onChanged: (String? value) {
 | |
|                         if (value != null) key = value.trim();
 | |
|                       },
 | |
|                     ),
 | |
|                     Offstage(
 | |
|                         offstage: !isInProgress,
 | |
|                         child: LinearProgressIndicator())
 | |
|                   ])),
 | |
|       actions: [
 | |
|         TextButton(
 | |
|           style: flatButtonStyle,
 | |
|           onPressed: () {
 | |
|             close();
 | |
|           },
 | |
|           child: Text(translate('Cancel')),
 | |
|         ),
 | |
|         TextButton(
 | |
|           style: flatButtonStyle,
 | |
|           onPressed: () async {
 | |
|             setState(() {
 | |
|               idServerMsg = null;
 | |
|               relayServerMsg = null;
 | |
|               apiServerMsg = null;
 | |
|               isInProgress = true;
 | |
|             });
 | |
|             if (await validate()) {
 | |
|               if (id != id0) {
 | |
|                 bind.mainSetOption(key: "custom-rendezvous-server", value: id);
 | |
|               }
 | |
|               if (relay != relay0) {
 | |
|                 bind.mainSetOption(key: "relay-server", value: relay);
 | |
|               }
 | |
|               if (key != key0) bind.mainSetOption(key: "key", value: key);
 | |
|               if (api != api0) {
 | |
|                 bind.mainSetOption(key: "api-server", value: api);
 | |
|               }
 | |
|               close();
 | |
|             }
 | |
|             setState(() {
 | |
|               isInProgress = false;
 | |
|             });
 | |
|           },
 | |
|           child: Text(translate('OK')),
 | |
|         ),
 | |
|       ],
 | |
|     );
 | |
|   });
 | |
| }
 | |
| 
 | |
| Future<String?> validateAsync(String value) async {
 | |
|   value = value.trim();
 | |
|   if (value.isEmpty) {
 | |
|     return null;
 | |
|   }
 | |
|   final res = await bind.mainTestIfValidServer(server: value);
 | |
|   return res.isEmpty ? null : res;
 | |
| }
 |