|
|
@ -4,8 +4,10 @@ import 'dart:math'; |
|
|
|
|
|
|
|
|
|
|
|
import 'package:camera/camera.dart'; |
|
|
|
import 'package:camera/camera.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
|
|
|
|
import 'package:image_picker/image_picker.dart'; |
|
|
|
|
|
|
|
|
|
|
|
import '../../flutter_zxing.dart'; |
|
|
|
import '../../flutter_zxing.dart'; |
|
|
|
|
|
|
|
import 'scan_mode_dropdown.dart'; |
|
|
|
|
|
|
|
|
|
|
|
/// Widget to scan a code from the camera stream |
|
|
|
/// Widget to scan a code from the camera stream |
|
|
|
class ReaderWidget extends StatefulWidget { |
|
|
|
class ReaderWidget extends StatefulWidget { |
|
|
@ -16,13 +18,18 @@ class ReaderWidget extends StatefulWidget { |
|
|
|
this.onMultiScan, |
|
|
|
this.onMultiScan, |
|
|
|
this.onMultiScanFailure, |
|
|
|
this.onMultiScanFailure, |
|
|
|
this.onControllerCreated, |
|
|
|
this.onControllerCreated, |
|
|
|
|
|
|
|
this.onMultiScanModeChanged, |
|
|
|
this.isMultiScan = false, |
|
|
|
this.isMultiScan = false, |
|
|
|
this.codeFormat = Format.any, |
|
|
|
this.codeFormat = Format.any, |
|
|
|
this.tryHarder = false, |
|
|
|
this.tryHarder = false, |
|
|
|
this.tryInverted = false, |
|
|
|
this.tryInverted = false, |
|
|
|
this.showScannerOverlay = true, |
|
|
|
this.showScannerOverlay = true, |
|
|
|
this.scannerOverlay, |
|
|
|
this.scannerOverlay, |
|
|
|
|
|
|
|
this.actionButtonsAlignment = Alignment.topCenter, |
|
|
|
|
|
|
|
this.actionButtonsPadding = const EdgeInsets.all(10), |
|
|
|
this.showFlashlight = true, |
|
|
|
this.showFlashlight = true, |
|
|
|
|
|
|
|
this.showToggleCamera = true, |
|
|
|
|
|
|
|
this.showGallery = true, |
|
|
|
this.allowPinchZoom = true, |
|
|
|
this.allowPinchZoom = true, |
|
|
|
this.scanDelay = const Duration(milliseconds: 1000), |
|
|
|
this.scanDelay = const Duration(milliseconds: 1000), |
|
|
|
this.scanDelaySuccess = const Duration(milliseconds: 1000), |
|
|
|
this.scanDelaySuccess = const Duration(milliseconds: 1000), |
|
|
@ -47,6 +54,9 @@ class ReaderWidget extends StatefulWidget { |
|
|
|
/// Called when the camera controller is created |
|
|
|
/// Called when the camera controller is created |
|
|
|
final Function(CameraController?)? onControllerCreated; |
|
|
|
final Function(CameraController?)? onControllerCreated; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Called when the multi scan mode is changed |
|
|
|
|
|
|
|
final Function(bool)? onMultiScanModeChanged; |
|
|
|
|
|
|
|
|
|
|
|
/// Allow multiple scans |
|
|
|
/// Allow multiple scans |
|
|
|
final bool isMultiScan; |
|
|
|
final bool isMultiScan; |
|
|
|
|
|
|
|
|
|
|
@ -65,9 +75,21 @@ class ReaderWidget extends StatefulWidget { |
|
|
|
/// Custom scanner overlay |
|
|
|
/// Custom scanner overlay |
|
|
|
final ScannerOverlay? scannerOverlay; |
|
|
|
final ScannerOverlay? scannerOverlay; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Align for action buttons |
|
|
|
|
|
|
|
final AlignmentGeometry actionButtonsAlignment; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Padding for action buttons |
|
|
|
|
|
|
|
final EdgeInsetsGeometry actionButtonsPadding; |
|
|
|
|
|
|
|
|
|
|
|
/// Show flashlight button |
|
|
|
/// Show flashlight button |
|
|
|
final bool showFlashlight; |
|
|
|
final bool showFlashlight; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Show toggle camera |
|
|
|
|
|
|
|
final bool showGallery; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Show toggle camera |
|
|
|
|
|
|
|
final bool showToggleCamera; |
|
|
|
|
|
|
|
|
|
|
|
/// Allow pinch zoom |
|
|
|
/// Allow pinch zoom |
|
|
|
final bool allowPinchZoom; |
|
|
|
final bool allowPinchZoom; |
|
|
|
|
|
|
|
|
|
|
@ -93,6 +115,7 @@ class ReaderWidget extends StatefulWidget { |
|
|
|
class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
with TickerProviderStateMixin, WidgetsBindingObserver { |
|
|
|
with TickerProviderStateMixin, WidgetsBindingObserver { |
|
|
|
List<CameraDescription> cameras = <CameraDescription>[]; |
|
|
|
List<CameraDescription> cameras = <CameraDescription>[]; |
|
|
|
|
|
|
|
CameraDescription? selectedCamera; |
|
|
|
CameraController? controller; |
|
|
|
CameraController? controller; |
|
|
|
bool _cameraOn = false; |
|
|
|
bool _cameraOn = false; |
|
|
|
|
|
|
|
|
|
|
@ -106,12 +129,15 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
// true when code detecting is ongoing |
|
|
|
// true when code detecting is ongoing |
|
|
|
bool _isProcessing = false; |
|
|
|
bool _isProcessing = false; |
|
|
|
|
|
|
|
|
|
|
|
Codes results = Codes(<Code>[], 0); |
|
|
|
bool isMultiScan = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Codes results = Codes(); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
void initState() { |
|
|
|
void initState() { |
|
|
|
super.initState(); |
|
|
|
super.initState(); |
|
|
|
WidgetsBinding.instance.addObserver(this); |
|
|
|
WidgetsBinding.instance.addObserver(this); |
|
|
|
|
|
|
|
isMultiScan = widget.isMultiScan; |
|
|
|
initStateAsync(); |
|
|
|
initStateAsync(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -123,7 +149,8 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
setState(() { |
|
|
|
setState(() { |
|
|
|
this.cameras = cameras; |
|
|
|
this.cameras = cameras; |
|
|
|
if (cameras.isNotEmpty) { |
|
|
|
if (cameras.isNotEmpty) { |
|
|
|
onNewCameraSelected(cameras.first); |
|
|
|
selectedCamera = cameras.first; |
|
|
|
|
|
|
|
onNewCameraSelected(selectedCamera); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -171,7 +198,10 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> onNewCameraSelected(CameraDescription cameraDescription) async { |
|
|
|
Future<void> onNewCameraSelected(CameraDescription? cameraDescription) async { |
|
|
|
|
|
|
|
if (cameraDescription == null) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
final CameraController? oldController = controller; |
|
|
|
final CameraController? oldController = controller; |
|
|
|
if (oldController != null) { |
|
|
|
if (oldController != null) { |
|
|
|
// controller?.removeListener(rebuildOnMount); |
|
|
|
// controller?.removeListener(rebuildOnMount); |
|
|
@ -235,7 +265,7 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
await Future<void>.delayed(widget.scanDelaySuccess); |
|
|
|
await Future<void>.delayed(widget.scanDelaySuccess); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
results = Codes(<Code>[], 0); |
|
|
|
results = Codes(); |
|
|
|
widget.onMultiScanFailure?.call(result); |
|
|
|
widget.onMultiScanFailure?.call(result); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -327,12 +357,58 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
controller?.setZoomLevel(_scaleFactor); |
|
|
|
controller?.setZoomLevel(_scaleFactor); |
|
|
|
}, |
|
|
|
}, |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
Align( |
|
|
|
|
|
|
|
alignment: widget.actionButtonsAlignment, |
|
|
|
|
|
|
|
child: Padding( |
|
|
|
|
|
|
|
padding: widget.actionButtonsPadding, |
|
|
|
|
|
|
|
child: ClipRRect( |
|
|
|
|
|
|
|
borderRadius: BorderRadius.circular(10), |
|
|
|
|
|
|
|
child: Container( |
|
|
|
|
|
|
|
color: Colors.black.withOpacity(0.5), |
|
|
|
|
|
|
|
child: Row( |
|
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
|
|
children: <Widget>[ |
|
|
|
if (widget.showFlashlight && isCameraReady) |
|
|
|
if (widget.showFlashlight && isCameraReady) |
|
|
|
Positioned( |
|
|
|
IconButton( |
|
|
|
bottom: 20, |
|
|
|
onPressed: _onFlashButtonTapped, |
|
|
|
left: 20, |
|
|
|
color: Colors.white, |
|
|
|
child: FloatingActionButton( |
|
|
|
icon: Icon( |
|
|
|
onPressed: () { |
|
|
|
_flashIcon( |
|
|
|
|
|
|
|
controller?.value.flashMode ?? FlashMode.off), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
if (widget.showGallery && isCameraReady) |
|
|
|
|
|
|
|
IconButton( |
|
|
|
|
|
|
|
onPressed: _onGalleryButtonTapped, |
|
|
|
|
|
|
|
color: Colors.white, |
|
|
|
|
|
|
|
icon: const Icon(Icons.photo_library), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
if (widget.showToggleCamera && isCameraReady) |
|
|
|
|
|
|
|
IconButton( |
|
|
|
|
|
|
|
onPressed: _onCameraButtonTapped, |
|
|
|
|
|
|
|
color: Colors.white, |
|
|
|
|
|
|
|
icon: const Icon(Icons.switch_camera), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
ScanModeDropdown( |
|
|
|
|
|
|
|
isMultiScan: isMultiScan, |
|
|
|
|
|
|
|
onChanged: (bool value) { |
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
|
|
|
isMultiScan = value; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
widget.onMultiScanModeChanged?.call(value); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _onFlashButtonTapped() { |
|
|
|
FlashMode mode = controller!.value.flashMode; |
|
|
|
FlashMode mode = controller!.value.flashMode; |
|
|
|
if (mode == FlashMode.torch) { |
|
|
|
if (mode == FlashMode.torch) { |
|
|
|
mode = FlashMode.off; |
|
|
|
mode = FlashMode.off; |
|
|
@ -341,31 +417,46 @@ class _ReaderWidgetState extends State<ReaderWidget> |
|
|
|
} |
|
|
|
} |
|
|
|
controller?.setFlashMode(mode); |
|
|
|
controller?.setFlashMode(mode); |
|
|
|
setState(() {}); |
|
|
|
setState(() {}); |
|
|
|
}, |
|
|
|
} |
|
|
|
backgroundColor: Colors.black26, |
|
|
|
|
|
|
|
child: _FlashIcon( |
|
|
|
Future<void> _onGalleryButtonTapped() async { |
|
|
|
flashMode: controller?.value.flashMode ?? FlashMode.off)), |
|
|
|
final XFile? file = |
|
|
|
|
|
|
|
await ImagePicker().pickImage(source: ImageSource.gallery); |
|
|
|
|
|
|
|
if (file != null) { |
|
|
|
|
|
|
|
final Code result = await zx.readBarcodeImagePath( |
|
|
|
|
|
|
|
file, |
|
|
|
|
|
|
|
params: DecodeParams( |
|
|
|
|
|
|
|
format: widget.codeFormat, |
|
|
|
|
|
|
|
tryHarder: widget.tryHarder, |
|
|
|
|
|
|
|
tryInverted: widget.tryInverted, |
|
|
|
|
|
|
|
isMultiScan: widget.isMultiScan, |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
if (result.isValid) { |
|
|
|
|
|
|
|
widget.onScan?.call(result); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
widget.onScanFailure?.call(result); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _FlashIcon extends StatelessWidget { |
|
|
|
void _onCameraButtonTapped() { |
|
|
|
const _FlashIcon({required this.flashMode}); |
|
|
|
final int cameraIndex = cameras.indexOf(controller!.description); |
|
|
|
final FlashMode flashMode; |
|
|
|
final int nextCameraIndex = (cameraIndex + 1) % cameras.length; |
|
|
|
|
|
|
|
selectedCamera = cameras[nextCameraIndex]; |
|
|
|
|
|
|
|
onNewCameraSelected(selectedCamera); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
IconData _flashIcon(FlashMode mode) { |
|
|
|
Widget build(BuildContext context) { |
|
|
|
switch (mode) { |
|
|
|
switch (flashMode) { |
|
|
|
|
|
|
|
case FlashMode.torch: |
|
|
|
case FlashMode.torch: |
|
|
|
return const Icon(Icons.flash_on); |
|
|
|
return Icons.flash_on; |
|
|
|
case FlashMode.off: |
|
|
|
case FlashMode.off: |
|
|
|
return const Icon(Icons.flash_off); |
|
|
|
return Icons.flash_off; |
|
|
|
case FlashMode.always: |
|
|
|
case FlashMode.always: |
|
|
|
return const Icon(Icons.flash_on); |
|
|
|
return Icons.flash_on; |
|
|
|
case FlashMode.auto: |
|
|
|
case FlashMode.auto: |
|
|
|
return const Icon(Icons.flash_auto); |
|
|
|
return Icons.flash_auto; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|