diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 2722427..eafb986 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -8,18 +8,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - with: - channel: 'stable' - - name: Version - run: flutter doctor -v - - name: Install dependencies - run: flutter pub get - - name: Format - run: dartfmt -n --set-exit-if-changed . - - name: Linter - run: dartanalyzer . --options=analysis_options.yaml --fatal-hints + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + - name: Version + run: flutter doctor -v + - name: Install dependencies + run: flutter pub get + - name: Format + run: flutter format -n --set-exit-if-changed . + - name: Linter + run: flutter analyze \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 180afc8..d4fcc1a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,107 +1 @@ -include: package:pedantic/analysis_options.yaml - -linter: - rules: - - always_put_required_named_parameters_first - - always_require_non_null_named_parameters - - avoid_annotating_with_dynamic - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_classes_with_only_static_members - - avoid_empty_else - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_print - - avoid_relative_lib_imports - - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - - avoid_returning_null_for_void - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - avoid_types_on_closure_parameters - - avoid_void_async - - await_only_futures - - camel_case_types - - cancel_subscriptions - - cascade_invocations - - close_sinks - - comment_references - - constant_identifier_names - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - - file_names - - hash_and_equals - - implementation_imports - - invariant_booleans - - iterable_contains_unrelated_type - - join_return_with_assignment - - library_names - - library_prefixes - - list_remove_unrelated_type - - no_duplicate_case_values - - non_constant_identifier_names - - null_closures - - only_throw_errors - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_declarations - - prefer_contains - - prefer_equal_for_default_values - - prefer_final_fields - - prefer_for_elements_to_map_fromIterable - - prefer_foreach - - prefer_function_declarations_over_variables - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_iterable_whereType - - prefer_null_aware_operators - - prefer_void_to_null - - provide_deprecation_message - - recursive_getters - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_pub_dependencies - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - - type_init_formals - - unawaited_futures - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - - unsafe_html - - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_string_buffers - - use_to_and_as_if_applicable - - valid_regexps - - void_checks +include: package:pedantic/analysis_options.yaml \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 8c837a8..8e7054e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -7,17 +7,13 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; void main() => runApp(MaterialApp(home: QRViewExample())); class QRViewExample extends StatefulWidget { - const QRViewExample({ - Key key, - }) : super(key: key); - @override State createState() => _QRViewExampleState(); } class _QRViewExampleState extends State { - Barcode result; - QRViewController controller; + Barcode? result; + 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 @@ -26,9 +22,9 @@ class _QRViewExampleState extends State { void reassemble() { super.reassemble(); if (Platform.isAndroid) { - controller.pauseCamera(); + controller!.pauseCamera(); } - controller.resumeCamera(); + controller!.resumeCamera(); } @override @@ -46,7 +42,7 @@ class _QRViewExampleState extends State { children: [ if (result != null) Text( - 'Barcode Type: ${describeEnum(result.format)} Data: ${result.code}') + 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') else Text('Scan a code'), Row( @@ -79,7 +75,7 @@ class _QRViewExampleState extends State { builder: (context, snapshot) { if (snapshot.data != null) { return Text( - 'Camera facing ${describeEnum(snapshot.data)}'); + 'Camera facing ${describeEnum(snapshot.data!)}'); } else { return Text('loading'); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 929acf1..b93026c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,17 +3,14 @@ description: Demonstrates how to use the flutter_qr plugin. publish_to: 'none' environment: - sdk: ">=2.6.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - dev_dependencies: + pedantic: ^1.11.0 flutter_test: sdk: flutter diff --git a/lib/src/lifecycle_event_handler.dart b/lib/src/lifecycle_event_handler.dart index f6da736..0580d17 100644 --- a/lib/src/lifecycle_event_handler.dart +++ b/lib/src/lifecycle_event_handler.dart @@ -3,28 +3,20 @@ import 'package:flutter/foundation.dart'; class LifecycleEventHandler extends WidgetsBindingObserver { LifecycleEventHandler({ - this.resumeCallBack, - this.suspendingCallBack, + required this.resumeCallBack, }); - final AsyncCallback resumeCallBack; - final AsyncCallback suspendingCallBack; + late final AsyncCallback resumeCallBack; @override Future didChangeAppLifecycleState(AppLifecycleState state) async { switch (state) { case AppLifecycleState.resumed: - if (resumeCallBack != null) { - await resumeCallBack(); - } + await resumeCallBack(); break; case AppLifecycleState.inactive: case AppLifecycleState.paused: case AppLifecycleState.detached: - if (suspendingCallBack != null) { - await suspendingCallBack(); - } - break; } } } diff --git a/lib/src/qr_code_scanner.dart b/lib/src/qr_code_scanner.dart index 67ee443..03a1a39 100644 --- a/lib/src/qr_code_scanner.dart +++ b/lib/src/qr_code_scanner.dart @@ -20,23 +20,21 @@ typedef PermissionSetCallback = void Function(QRViewController, bool); /// and the barcode scanner gets displayed. class QRView extends StatefulWidget { const QRView({ - @required Key key, - @required this.onQRViewCreated, + required Key key, + required this.onQRViewCreated, this.overlay, this.overlayMargin = EdgeInsets.zero, this.cameraFacing = CameraFacing.back, this.onPermissionSet, - this.formatsAllowed, - }) : assert(key != null), - assert(onQRViewCreated != null), - super(key: key); + this.formatsAllowed = const [], + }) : super(key: key); /// [onQRViewCreated] gets called when the view is created final QRViewCreatedCallback onQRViewCreated; /// Use [overlay] to provide an overlay for the view. /// This can be used to create a certain scan area. - final QrScannerOverlayShape overlay; + final QrScannerOverlayShape? overlay; /// Use [overlayMargin] to provide a margin to [overlay] final EdgeInsetsGeometry overlayMargin; @@ -48,7 +46,7 @@ class QRView extends StatefulWidget { final CameraFacing cameraFacing; /// Calls the provided [onPermissionSet] callback when the permission is set. - final PermissionSetCallback onPermissionSet; + final PermissionSetCallback? onPermissionSet; /// Use [formatsAllowed] to specify which formats needs to be scanned. final List formatsAllowed; @@ -58,21 +56,14 @@ class QRView extends StatefulWidget { } class _QRViewState extends State { - var _channel; - var _observer; + late MethodChannel _channel; + late LifecycleEventHandler _observer; @override void initState() { super.initState(); - _observer = LifecycleEventHandler( - resumeCallBack: () async => { - if (_channel != null) - { - QRViewController.updateDimensions(widget.key, _channel, - overlay: widget.overlay) - } - }); - WidgetsBinding.instance.addObserver(_observer); + _observer = LifecycleEventHandler(resumeCallBack: updateDimensions); + WidgetsBinding.instance!.addObserver(_observer); } @override @@ -90,15 +81,17 @@ class _QRViewState extends State { @override void dispose() { super.dispose(); - WidgetsBinding.instance.removeObserver(_observer); + WidgetsBinding.instance!.removeObserver(_observer); } - bool onNotification(notification) { - Future.microtask(() => { - QRViewController.updateDimensions(widget.key, _channel, - overlay: widget.overlay) - }); + Future updateDimensions() async { + await QRViewController.updateDimensions( + widget.key as GlobalKey>, _channel, + overlay: widget.overlay); + } + bool onNotification(notification) { + updateDimensions(); return false; } @@ -109,7 +102,7 @@ class _QRViewState extends State { Container( padding: widget.overlayMargin, decoration: ShapeDecoration( - shape: widget.overlay, + shape: widget.overlay!, ), ) ], @@ -139,7 +132,7 @@ class _QRViewState extends State { break; default: throw UnsupportedError( - "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); + "Trying to use the default qrview implementation for $defaultTargetPlatform but there isn't a default one"); } return _platformQrView; } @@ -149,19 +142,21 @@ class _QRViewState extends State { // Start scan after creation of the view final controller = QRViewController._( - _channel, widget.key, widget.onPermissionSet, widget.cameraFacing) - .._startScan(widget.key, widget.overlay, widget.formatsAllowed); + _channel, + widget.key as GlobalKey>?, + widget.onPermissionSet, + widget.cameraFacing) + .._startScan(widget.key as GlobalKey>, + widget.overlay, widget.formatsAllowed); // Initialize the controller for controlling the QRView - if (widget.onQRViewCreated != null) { - widget.onQRViewCreated(controller); - } + widget.onQRViewCreated(controller); } } class _QrCameraSettings { _QrCameraSettings({ - this.cameraFacing, + this.cameraFacing = CameraFacing.unknown, }); final CameraFacing cameraFacing; @@ -174,8 +169,8 @@ class _QrCameraSettings { } class QRViewController { - QRViewController._(MethodChannel channel, GlobalKey qrKey, - PermissionSetCallback onPermissionSet, CameraFacing cameraFacing) + QRViewController._(MethodChannel channel, GlobalKey? qrKey, + PermissionSetCallback? onPermissionSet, CameraFacing cameraFacing) : _channel = channel, _cameraFacing = cameraFacing { _channel.setMethodCallHandler((call) async { @@ -186,9 +181,9 @@ class QRViewController { final code = args['code'] as String; final rawType = args['type'] as String; // Raw bytes are only supported by Android. - final rawBytes = args['rawBytes'] as List; + final rawBytes = args['rawBytes'] as List?; final format = BarcodeTypesExtension.fromString(rawType); - if (format != null) { + if (format != BarcodeFormat.unknown) { final barcode = Barcode(code, format, rawBytes); _scanUpdateController.sink.add(barcode); } else { @@ -197,15 +192,14 @@ class QRViewController { } break; case 'onPermissionSet': - await getSystemFeatures(); // if we have no permission all features will not be avaible - if (call.arguments != null) { - if (call.arguments as bool) { + if (call.arguments != null && call.arguments is bool) { + if (call.arguments) { _hasPermissions = true; } else { _hasPermissions = false; } if (onPermissionSet != null) { - onPermissionSet(this, call.arguments as bool); + onPermissionSet(this, call.arguments); } } break; @@ -220,20 +214,17 @@ class QRViewController { Stream get scannedDataStream => _scanUpdateController.stream; - SystemFeatures _features; - bool _hasPermissions; - - SystemFeatures get systemFeatures => _features; + bool _hasPermissions = false; bool get hasPermissions => _hasPermissions; /// Starts the barcode scanner - Future _startScan(GlobalKey key, QrScannerOverlayShape overlay, - List barcodeFormats) async { + Future _startScan(GlobalKey key, QrScannerOverlayShape? overlay, + List? barcodeFormats) async { // We need to update the dimension before the scan is started. try { await QRViewController.updateDimensions(key, _channel, overlay: overlay); return await _channel.invokeMethod( - 'startScan', barcodeFormats?.map((e) => e.asInt())?.toList() ?? []); + 'startScan', barcodeFormats?.map((e) => e.asInt()).toList() ?? []); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -262,7 +253,7 @@ class QRViewController { } /// Get flashlight status - Future getFlashStatus() async { + Future getFlashStatus() async { try { return await _channel.invokeMethod('getFlashInfo'); } on PlatformException catch (e) { @@ -273,7 +264,7 @@ class QRViewController { /// Toggles the flashlight between available modes Future toggleFlash() async { try { - await _channel.invokeMethod('toggleFlash') as bool; + await _channel.invokeMethod('toggleFlash') as bool?; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -311,7 +302,10 @@ class QRViewController { try { var features = await _channel.invokeMapMethod('getSystemFeatures'); - return SystemFeatures.fromJson(features); + if (features != null) { + return SystemFeatures.fromJson(features); + } + throw CameraException('Error', 'Could not get system features'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -324,12 +318,13 @@ class QRViewController { } /// Updates the view dimensions for iOS. - static Future updateDimensions(GlobalKey key, MethodChannel channel, - {QrScannerOverlayShape overlay}) async { + static Future updateDimensions(GlobalKey key, MethodChannel channel, + {QrScannerOverlayShape? overlay}) async { if (defaultTargetPlatform == TargetPlatform.iOS) { - // Add small delay to ensure the renderbox is loaded + // Add small delay to ensure the render box is loaded await Future.delayed(Duration(milliseconds: 100)); - final RenderBox renderBox = key.currentContext.findRenderObject(); + if (key.currentContext == null) return false; + final renderBox = key.currentContext!.findRenderObject() as RenderBox; try { await channel.invokeMethod('setDimensions', { 'width': renderBox.size.width, @@ -337,9 +332,11 @@ class QRViewController { 'scanArea': overlay?.cutOutSize ?? 0, 'scanAreaOffset': overlay?.cutOutBottomOffset ?? 0 }); + return true; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } + return false; } } diff --git a/lib/src/qr_scanner_overlay_shape.dart b/lib/src/qr_scanner_overlay_shape.dart index 77c1a14..1b11dfb 100644 --- a/lib/src/qr_scanner_overlay_shape.dart +++ b/lib/src/qr_scanner_overlay_shape.dart @@ -11,10 +11,7 @@ class QrScannerOverlayShape extends ShapeBorder { this.borderLength = 40, this.cutOutSize = 250, this.cutOutBottomOffset = 0, - }) : assert( - cutOutSize != null ?? - cutOutSize != null ?? - borderLength <= cutOutSize / 2 + borderWidth * 2, + }) : assert(borderLength <= cutOutSize / 2 + borderWidth * 2, "Border can't be larger than ${cutOutSize / 2 + borderWidth * 2}"); final Color borderColor; @@ -29,14 +26,14 @@ class QrScannerOverlayShape extends ShapeBorder { EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10); @override - Path getInnerPath(Rect rect, {TextDirection textDirection}) { + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { return Path() ..fillType = PathFillType.evenOdd ..addPath(getOuterPath(rect), Offset.zero); } @override - Path getOuterPath(Rect rect, {TextDirection textDirection}) { + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { Path _getLeftTopPath(Rect rect) { return Path() ..moveTo(rect.left, rect.bottom) @@ -60,7 +57,7 @@ class QrScannerOverlayShape extends ShapeBorder { } @override - void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) { + void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { final width = rect.width; final borderWidthSize = width / 2; final height = rect.height; @@ -68,9 +65,7 @@ class QrScannerOverlayShape extends ShapeBorder { final _borderLength = borderLength > cutOutSize / 2 + borderWidth * 2 ? borderWidthSize / 2 : borderLength; - final _cutOutSize = cutOutSize != null && cutOutSize < width - ? cutOutSize - : width - borderOffset; + final _cutOutSize = cutOutSize < width ? cutOutSize : width - borderOffset; final backgroundPaint = Paint() ..color = overlayColor diff --git a/lib/src/types/barcode.dart b/lib/src/types/barcode.dart index 9bfffdb..9627a9f 100644 --- a/lib/src/types/barcode.dart +++ b/lib/src/types/barcode.dart @@ -12,5 +12,5 @@ class Barcode { final BarcodeFormat format; /// Raw bytes are only supported by Android. - final List rawBytes; + final List? rawBytes; } diff --git a/lib/src/types/barcode_format.dart b/lib/src/types/barcode_format.dart index 4d93fd5..49bcee2 100644 --- a/lib/src/types/barcode_format.dart +++ b/lib/src/types/barcode_format.dart @@ -53,7 +53,10 @@ enum BarcodeFormat { upcE, /// UPC/EAN extension format. Not a stand-alone format. - upcEanExtension + upcEanExtension, + + /// Unknown + unknown } extension BarcodeTypesExtension on BarcodeFormat { @@ -65,57 +68,40 @@ extension BarcodeTypesExtension on BarcodeFormat { switch (format) { case 'AZTEC': return BarcodeFormat.aztec; - break; case 'CODABAR': return BarcodeFormat.codabar; - break; case 'CODE_39': return BarcodeFormat.code39; - break; case 'CODE_93': return BarcodeFormat.code93; - break; case 'CODE_128': return BarcodeFormat.code128; - break; case 'DATA_MATRIX': return BarcodeFormat.dataMatrix; - break; case 'EAN_8': return BarcodeFormat.ean8; - break; case 'EAN_13': return BarcodeFormat.ean13; - break; case 'ITF': return BarcodeFormat.itf; - break; case 'MAXICODE': return BarcodeFormat.maxicode; - break; case 'PDF_417': return BarcodeFormat.pdf417; - break; case 'QR_CODE': return BarcodeFormat.qrcode; - break; case 'RSS14': return BarcodeFormat.rss14; - break; case 'RSS_EXPANDED': return BarcodeFormat.rssExpanded; - break; case 'UPC_A': return BarcodeFormat.upcA; - break; case 'UPC_E': return BarcodeFormat.upcE; - break; case 'UPC_EAN_EXTENSION': return BarcodeFormat.upcEanExtension; - break; default: - return null; + return BarcodeFormat.unknown; } } @@ -123,57 +109,40 @@ extension BarcodeTypesExtension on BarcodeFormat { switch (this) { case BarcodeFormat.aztec: return 'AZTEC'; - break; case BarcodeFormat.codabar: return 'CODABAR'; - break; case BarcodeFormat.code39: return 'CODE_39'; - break; case BarcodeFormat.code93: return 'CODE_93'; - break; case BarcodeFormat.code128: return 'CODE_128'; - break; case BarcodeFormat.dataMatrix: return 'DATA_MATRIX'; - break; case BarcodeFormat.ean8: return 'EAN_8'; - break; case BarcodeFormat.ean13: return 'EAN_13'; - break; case BarcodeFormat.itf: return 'ITF'; - break; case BarcodeFormat.maxicode: return 'MAXICODE'; - break; case BarcodeFormat.pdf417: return 'PDF_417'; - break; case BarcodeFormat.qrcode: return 'QR_CODE'; - break; case BarcodeFormat.rss14: return 'RSS14'; - break; case BarcodeFormat.rssExpanded: return 'RSS_EXPANDED'; - break; case BarcodeFormat.upcA: return 'UPC_A'; - break; case BarcodeFormat.upcE: return 'UPC_E'; - break; case BarcodeFormat.upcEanExtension: return 'UPC_EAN_EXTENSION'; - break; default: - return 'NOT_VALID'; + return 'UNKNOWN'; } } } diff --git a/lib/src/types/camera.dart b/lib/src/types/camera.dart index 532f3b4..67fe8cb 100644 --- a/lib/src/types/camera.dart +++ b/lib/src/types/camera.dart @@ -3,5 +3,8 @@ enum CameraFacing { back, /// Shows front facing camera. - front + front, + + /// Unknown camera + unknown } diff --git a/lib/src/types/camera_exception.dart b/lib/src/types/camera_exception.dart index 740f4b6..0abb639 100644 --- a/lib/src/types/camera_exception.dart +++ b/lib/src/types/camera_exception.dart @@ -7,7 +7,7 @@ class CameraException implements Exception { String code; /// Textual description of the error. - String description; + String? description; @override String toString() => 'CameraException($code, $description)'; diff --git a/pubspec.yaml b/pubspec.yaml index 2532a38..bdddd05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://juliuscanute.com repository: https://github.com/juliuscanute/qr_code_scanner environment: - sdk: ">=2.6.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.10.0" dependencies: @@ -13,7 +13,7 @@ dependencies: sdk: flutter dev_dependencies: - pedantic: ^1.9.2 + pedantic: ^1.11.0 flutter: plugin: