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 { 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 { backToHomePage(); await controller?.pauseCamera(); if (!data.startsWith('config=')) { showToast('Invalid QR code'); return; } try { Map 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 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 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: [ 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); gFFI.ffiModel.updateUser(); close(); } setState(() { isInProgress = false; }); }, child: Text(translate('OK')), ), ], ); }); } Future validateAsync(String value) async { value = value.trim(); if (value.isEmpty) { return null; } final res = await bind.mainTestIfValidServer(server: value); return res.isEmpty ? null : res; }