From b387b1b9c9d526a77fd334b3b94fcd14b1ac8ff3 Mon Sep 17 00:00:00 2001 From: sss_rsantos13 Date: Wed, 14 Dec 2022 10:21:39 -0300 Subject: [PATCH] - Added callback for qrcode read timeout - Added callback for qrcode read error - Added flag to decide whether camera capture should be stopped if reading time runs out - Added field to set qrcode read timeout - increase lib version --- .../qrmobilevision/QrDetector.java | 1 + .../qrmobilevision/QrMobileVisionPlugin.java | 14 ++++- .../rmtmckenzie/qrmobilevision/QrReader.java | 15 +++-- .../qrmobilevision/QrReaderCallbacks.java | 2 + example/lib/main.dart | 61 ++++++++++++++----- lib/src/qr_camera.dart | 12 ++++ lib/src/qr_channel_reader.dart | 21 +++++++ lib/src/qr_mobile_vision.dart | 41 +++++++++---- 8 files changed, 132 insertions(+), 35 deletions(-) diff --git a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrDetector.java b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrDetector.java index 8def9a82..0a9c20f7 100644 --- a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrDetector.java +++ b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrDetector.java @@ -90,5 +90,6 @@ public void onSuccess(List firebaseVisionBarcodes) { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Barcode Reading Failure: ", e); + communicator.qrReadError(e); } } diff --git a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrMobileVisionPlugin.java b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrMobileVisionPlugin.java index 21582827..fcc2a39d 100644 --- a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrMobileVisionPlugin.java +++ b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrMobileVisionPlugin.java @@ -123,6 +123,7 @@ public void onMethodCall(MethodCall methodCall, Result result) { Integer targetHeight = methodCall.argument("targetHeight"); Integer cameraDirection = methodCall.argument("cameraDirection"); List formatStrings = methodCall.argument("formats"); + Boolean shouldStopCameraOnReadTimeout = methodCall.argument("shouldStopCameraOnReadTimeout"); if (targetWidth == null || targetHeight == null) { result.error("INVALID_ARGUMENT", "Missing a required argument", "Expecting targetWidth, targetHeight, and optionally heartbeatTimeout"); @@ -139,7 +140,8 @@ public void onMethodCall(MethodCall methodCall, Result result) { try { reader.start( lastHeartbeatTimeout == null ? 0 : lastHeartbeatTimeout, - cameraDirection == null ? 0 : cameraDirection + cameraDirection == null ? 0 : cameraDirection, + shouldStopCameraOnReadTimeout != null && shouldStopCameraOnReadTimeout ); } catch (IOException e) { e.printStackTrace(); @@ -186,6 +188,16 @@ public void qrRead(String data) { channel.invokeMethod("qrRead", data); } + @Override + public void qrReadTimeout() { + channel.invokeMethod("qrReadTimeout", null); + } + + @Override + public void qrReadError(Throwable error) { + channel.invokeMethod("qrReadError", stackTraceAsString(error.getStackTrace())); + } + @Override public void started() { Map response = new HashMap<>(); diff --git a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReader.java b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReader.java index 1e5ec20f..fc336a2b 100644 --- a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReader.java +++ b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReader.java @@ -19,12 +19,14 @@ class QrReader { private final Activity context; private final QRReaderStartedCallback startedCallback; private Heartbeat heartbeat; + private final QrReaderCallbacks communicator; QrReader(int width, int height, Activity context, BarcodeScannerOptions options, final QRReaderStartedCallback startedCallback, final QrReaderCallbacks communicator, final SurfaceTexture texture) { this.context = context; this.startedCallback = startedCallback; + this.communicator = communicator; if (android.os.Build.VERSION.SDK_INT >= 23) { Log.i(TAG, "Using new camera API."); @@ -35,7 +37,7 @@ class QrReader { } } - void start(final int heartBeatTimeout, final int cameraDirection) throws IOException, NoPermissionException, Exception { + void start(final int heartBeatTimeout, final int cameraDirection, final boolean shouldStopCameraOnReadTimeout) throws IOException, NoPermissionException, Exception { if (!hasCameraHardware(context)) { throw new Exception(Exception.Reason.noHardware); } @@ -43,19 +45,20 @@ void start(final int heartBeatTimeout, final int cameraDirection) throws IOExcep if (!checkCameraPermission(context)) { throw new NoPermissionException(); } else { - continueStarting(heartBeatTimeout, cameraDirection); + continueStarting(heartBeatTimeout, cameraDirection, shouldStopCameraOnReadTimeout); } } - private void continueStarting(int heartBeatTimeout, final int cameraDirection) throws IOException { + private void continueStarting(int heartBeatTimeout, final int cameraDirection, final boolean shouldStopCameraOnReadTimeout) throws IOException { try { if (heartBeatTimeout > 0) { if (heartbeat != null) { heartbeat.stop(); } - heartbeat = new Heartbeat(heartBeatTimeout, new Runnable() { - @Override - public void run() { + heartbeat = new Heartbeat(heartBeatTimeout, () -> { + Log.i(TAG, "Stopping camera from Heartbeat."); + communicator.qrReadTimeout(); + if (shouldStopCameraOnReadTimeout) { stop(); } }); diff --git a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReaderCallbacks.java b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReaderCallbacks.java index 6edbc6e5..357e4fa6 100644 --- a/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReaderCallbacks.java +++ b/android/src/main/java/com/github/rmtmckenzie/qrmobilevision/QrReaderCallbacks.java @@ -2,4 +2,6 @@ public interface QrReaderCallbacks { void qrRead(String data); + void qrReadTimeout(); + void qrReadError(Throwable error); } diff --git a/example/lib/main.dart b/example/lib/main.dart index eecbe35a..a03e06f9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -49,7 +49,9 @@ class _MyAppState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Back"), - Switch(value: dirState, onChanged: (val) => setState(() => dirState = val)), + Switch( + value: dirState, + onChanged: (val) => setState(() => dirState = val)), Text("Front"), ], ), @@ -60,26 +62,41 @@ class _MyAppState extends State { width: 300.0, height: 600.0, child: QrCamera( - onError: (context, error) => Text( - error.toString(), - style: TextStyle(color: Colors.red), + shouldStopCameraOnReadTimeout: false, + qrCodeReadTimeout: 5000, + qrReadTimeoutCallback: () { + debugPrint('Qrcode read timeout'); + }, + qrReadErrorCallback: (error) { + debugPrint('QrCode read error: $error'); + }, + onError: (context, error) => buildPortrait( + child: Center( + child: Text( + error.toString(), + style: TextStyle(color: Colors.red), + ), + ), + ), + notStartedBuilder: (context) => buildPortrait( + child: Center( + child: Text("Camera Loading ..."), + ), + ), + offscreenBuilder: (context) => buildPortrait( + child: Center( + child: Text("Camera Paused."), + ), ), - cameraDirection: dirState ? CameraDirection.FRONT : CameraDirection.BACK, + cameraDirection: dirState + ? CameraDirection.FRONT + : CameraDirection.BACK, qrCodeCallback: (code) { setState(() { qr = code; }); }, - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - border: Border.all( - color: Colors.orange, - width: 10.0, - style: BorderStyle.solid, - ), - ), - ), + child: buildPortrait(), ), ), ) @@ -100,4 +117,18 @@ class _MyAppState extends State { }), ); } + + Widget buildPortrait({Widget child}) { + return Container( + child: child, + decoration: BoxDecoration( + color: Colors.black54, + border: Border.all( + color: Colors.orange, + width: 10.0, + style: BorderStyle.solid, + ), + ), + ); + } } diff --git a/lib/src/qr_camera.dart b/lib/src/qr_camera.dart index 2895eb79..cf7b7e4f 100644 --- a/lib/src/qr_camera.dart +++ b/lib/src/qr_camera.dart @@ -26,7 +26,11 @@ class QrCamera extends StatefulWidget { WidgetBuilder? offscreenBuilder, ErrorCallback? onError, this.cameraDirection = CameraDirection.BACK, + this.shouldStopCameraOnReadTimeout = false, this.formats, + this.qrCodeReadTimeout = 0, + this.qrReadErrorCallback, + this.qrReadTimeoutCallback, }) : notStartedBuilder = notStartedBuilder ?? _defaultNotStartedBuilder, offscreenBuilder = offscreenBuilder ?? notStartedBuilder ?? _defaultOffscreenBuilder, @@ -35,12 +39,16 @@ class QrCamera extends StatefulWidget { final BoxFit fit; final ValueChanged qrCodeCallback; + final ValueChanged? qrReadErrorCallback; + final VoidCallback? qrReadTimeoutCallback; final Widget? child; final WidgetBuilder notStartedBuilder; final WidgetBuilder offscreenBuilder; final ErrorCallback onError; final List? formats; final CameraDirection cameraDirection; + final int qrCodeReadTimeout; + final bool shouldStopCameraOnReadTimeout; static toggleFlash() { QrMobileVision.toggleFlash(); @@ -109,6 +117,10 @@ class QrCameraState extends State with WidgetsBindingObserver { qrCodeHandler: widget.qrCodeCallback, formats: widget.formats, cameraDirection: widget.cameraDirection, + qrReadTimeoutHandler: widget.qrReadTimeoutCallback, + qrCodeReadErrorHandler: widget.qrReadErrorCallback, + qrCodeReadTimeout: widget.qrCodeReadTimeout, + shouldStopCameraOnReadTimeout: widget.shouldStopCameraOnReadTimeout, ); } diff --git a/lib/src/qr_channel_reader.dart b/lib/src/qr_channel_reader.dart index 5292d20c..e8592bea 100644 --- a/lib/src/qr_channel_reader.dart +++ b/lib/src/qr_channel_reader.dart @@ -11,6 +11,17 @@ class QrChannelReader { qrCodeHandler!(call.arguments); } break; + case 'qrReadTimeout': + if (qrCodeReadTimeoutHandler != null) { + qrCodeReadTimeoutHandler!.call(); + } + break; + case 'qrReadError': + if (qrCodeErrorHandler != null) { + assert(call.arguments is String); + qrCodeErrorHandler!(call.arguments); + } + break; default: print("QrChannelHandler: unknown method call received at " "${call.method}"); @@ -22,6 +33,16 @@ class QrChannelReader { this.qrCodeHandler = qrch; } + void setQrCodeErrorHandler(ValueChanged? qrch) { + this.qrCodeErrorHandler = qrch; + } + + void setQrCodeReadTimeoutHandler(VoidCallback? qrch) { + this.qrCodeReadTimeoutHandler = qrch; + } + MethodChannel channel; ValueChanged? qrCodeHandler; + ValueChanged? qrCodeErrorHandler; + VoidCallback? qrCodeReadTimeoutHandler; } diff --git a/lib/src/qr_mobile_vision.dart b/lib/src/qr_mobile_vision.dart index 47557690..30888929 100644 --- a/lib/src/qr_mobile_vision.dart +++ b/lib/src/qr_mobile_vision.dart @@ -10,31 +10,42 @@ import 'package:qr_mobile_vision/src/preview_details.dart'; import 'package:qr_mobile_vision/src/qr_channel_reader.dart'; class QrMobileVision { - static const MethodChannel _channel = const MethodChannel('com.github.rmtmckenzie/qr_mobile_vision'); + static const MethodChannel _channel = + const MethodChannel('com.github.rmtmckenzie/qr_mobile_vision'); static QrChannelReader channelReader = QrChannelReader(_channel); //Set target size before starting - static Future start({ - required int width, - required int height, - required ValueChanged qrCodeHandler, - CameraDirection cameraDirection = CameraDirection.BACK, - List? formats = defaultBarcodeFormats, - }) async { + static Future start( + {required int width, + required int height, + required ValueChanged qrCodeHandler, + CameraDirection cameraDirection = CameraDirection.BACK, + List? formats = defaultBarcodeFormats, + VoidCallback? qrReadTimeoutHandler, + ValueChanged? qrCodeReadErrorHandler, + int qrCodeReadTimeout = 0, + bool shouldStopCameraOnReadTimeout = false}) async { final _formats = formats ?? defaultBarcodeFormats; assert(_formats.length > 0); - List formatStrings = _formats.map((format) => format.toString().split('.')[1]).toList(growable: false); + List formatStrings = _formats + .map((format) => format.toString().split('.')[1]) + .toList(growable: false); - final deviceInfoFut = Platform.isAndroid ? DeviceInfoPlugin().androidInfo : Future.value(null); + final deviceInfoFut = Platform.isAndroid + ? DeviceInfoPlugin().androidInfo + : Future.value(null); channelReader.setQrCodeHandler(qrCodeHandler); + channelReader.setQrCodeReadTimeoutHandler(qrReadTimeoutHandler); + channelReader.setQrCodeErrorHandler(qrCodeReadErrorHandler); final details = (await _channel.invokeMapMethod('start', { 'targetWidth': width, 'targetHeight': height, - 'heartbeatTimeout': 0, + 'heartbeatTimeout': qrCodeReadTimeout, 'cameraDirection': (cameraDirection == CameraDirection.FRONT ? 0 : 1), 'formats': formatStrings, + 'shouldStopCameraOnReadTimeout': shouldStopCameraOnReadTimeout, }))!; int? textureId = details["textureId"]; @@ -42,7 +53,8 @@ class QrMobileVision { num surfaceHeight = details["surfaceHeight"]; num surfaceWidth = details["surfaceWidth"]; - final deets = await NativePreviewDetails(surfaceWidth, surfaceHeight, orientation, textureId); + final deets = await NativePreviewDetails( + surfaceWidth, surfaceHeight, orientation, textureId); final devInfo = await deviceInfoFut; return PreviewDetails(deets, devInfo?.version.sdkInt ?? -1); @@ -54,6 +66,8 @@ class QrMobileVision { static Future stop() { channelReader.setQrCodeHandler(null); + channelReader.setQrCodeReadTimeoutHandler(null); + channelReader.setQrCodeErrorHandler(null); return _channel.invokeMethod('stop').catchError(print); } @@ -62,6 +76,7 @@ class QrMobileVision { } static Future>?> getSupportedSizes() { - return _channel.invokeMethod('getSupportedSizes').catchError(print) as Future>?>; + return _channel.invokeMethod('getSupportedSizes').catchError(print) + as Future>?>; } }