Skip to content

Commit

Permalink
feat: Replace mobile scanner #1725
Browse files Browse the repository at this point in the history
  • Loading branch information
bibash28 committed Jul 19, 2023
1 parent c47c3fc commit d1ea233
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 101 deletions.
19 changes: 17 additions & 2 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,33 @@ target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end


$iOSVersion = '14.0'

post_install do |installer|
# Google's ML Kit Barcode Scanning setup
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7"
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end

installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# polygonid-setup
# polygonid-setup
target.build_configurations.each do |config|
cflags = config.build_settings['OTHER_CFLAGS'] || ['$(inherited)']
cflags << '-fembed-bitcode'
config.build_settings['OTHER_CFLAGS'] = cflags
config.build_settings['SWIFT_VERSION'] = '5.0'
config.build_settings['ENABLE_BITCODE'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
# Google's ML Kit Barcode Scanning setup
target.build_configurations.each do |config|
if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
end
if target.name == "Pods-Runner"
puts "Updating #{target.name} OTHER_LDFLAGS"
target.build_configurations.each do |config|
Expand Down
1 change: 1 addition & 0 deletions lib/dashboard/qr_code/qr_code_scan/qr_code_scan.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export 'cubit/qr_code_scan_cubit.dart';
export 'model/siopv2_param.dart';
export 'view/qr_camera_view.dart';
export 'view/qr_code_scan_page.dart';
export 'view/qr_scanner_page.dart';
242 changes: 242 additions & 0 deletions lib/dashboard/qr_code/qr_code_scan/view/qr_camera_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_mlkit_commons/google_mlkit_commons.dart';

class QrCameraView extends StatefulWidget {
const QrCameraView(
{super.key,
required this.onImage,
this.onCameraFeedReady,
this.onDetectorViewModeChanged,
this.onCameraLensDirectionChanged,
this.initialCameraLensDirection = CameraLensDirection.back});

final Function(InputImage inputImage) onImage;
final VoidCallback? onCameraFeedReady;
final VoidCallback? onDetectorViewModeChanged;
final Function(CameraLensDirection direction)? onCameraLensDirectionChanged;
final CameraLensDirection initialCameraLensDirection;

@override
State<QrCameraView> createState() => _QrCameraViewState();
}

class _QrCameraViewState extends State<QrCameraView> {
static List<CameraDescription> _cameras = [];
CameraController? _controller;
int _cameraIndex = -1;
double _currentZoomLevel = 1.0;
double _minAvailableZoom = 1.0;
double _maxAvailableZoom = 1.0;
double _minAvailableExposureOffset = 0.0;
double _maxAvailableExposureOffset = 0.0;
double _currentExposureOffset = 0.0;
bool _changingCameraLens = false;

@override
void initState() {
super.initState();

_initialize();
}

void _initialize() async {
if (_cameras.isEmpty) {
_cameras = await availableCameras();
}
for (var i = 0; i < _cameras.length; i++) {
if (_cameras[i].lensDirection == widget.initialCameraLensDirection) {
_cameraIndex = i;
break;
}
}
if (_cameraIndex != -1) {
_startLiveFeed();
}
}

@override
void dispose() {
_stopLiveFeed();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(body: _liveFeedBody());
}

Widget _liveFeedBody() {
if (_cameras.isEmpty) return Container();
if (_controller == null) return Container();
if (_controller?.value.isInitialized == false) return Container();
return ColoredBox(
color: Colors.black,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Center(
child: _changingCameraLens
? Container()
: CameraPreview(
_controller!,
child: null,
),
)
],
),
);
}

Future<void> _startLiveFeed() async {
final camera = _cameras[_cameraIndex];
_controller = CameraController(
camera,
// Set to ResolutionPreset.high. Do NOT set it to ResolutionPreset.max because for some phones does NOT work.
ResolutionPreset.high,
enableAudio: false,
imageFormatGroup: Platform.isAndroid
? ImageFormatGroup.nv21
: ImageFormatGroup.bgra8888,
);
await _controller?.initialize().then((_) {
if (!mounted) {
return;
}
_controller?.getMinZoomLevel().then((value) {
_currentZoomLevel = value;
_minAvailableZoom = value;
});
_controller?.getMaxZoomLevel().then((value) {
_maxAvailableZoom = value;
});
_currentExposureOffset = 0.0;
_controller?.getMinExposureOffset().then((value) {
_minAvailableExposureOffset = value;
});
_controller?.getMaxExposureOffset().then((value) {
_maxAvailableExposureOffset = value;
});
_controller?.startImageStream(_processCameraImage).then((value) {
if (widget.onCameraFeedReady != null) {
widget.onCameraFeedReady!();
}
if (widget.onCameraLensDirectionChanged != null) {
widget.onCameraLensDirectionChanged!(camera.lensDirection);
}
});
setState(() {});
});
}

Future<void> _stopLiveFeed() async {
await _controller?.stopImageStream();
await _controller?.dispose();
_controller = null;
}

void _processCameraImage(CameraImage image) {
final inputImage = _inputImageFromCameraImage(image);
if (inputImage == null) return;
widget.onImage(inputImage);
}

final _orientations = {
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeLeft: 90,
DeviceOrientation.portraitDown: 180,
DeviceOrientation.landscapeRight: 270,
};

InputImage? _inputImageFromCameraImage(CameraImage image) {
if (_controller == null) return null;

// get image rotation
// it is used in android to convert the InputImage from Dart to Java: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java
// `rotation` is not used in iOS to convert the InputImage from Dart to Obj-C: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/ios/Classes/MLKVisionImage%2BFlutterPlugin.m
// in both platforms `rotation` and `camera.lensDirection` can be used to compensate `x` and `y` coordinates on a canvas: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/example/lib/vision_detector_views/painters/coordinates_translator.dart
final camera = _cameras[_cameraIndex];
final sensorOrientation = camera.sensorOrientation;
// print(
// 'lensDirection: ${camera.lensDirection}, sensorOrientation: $sensorOrientation, ${_controller?.value.deviceOrientation} ${_controller?.value.lockedCaptureOrientation} ${_controller?.value.isCaptureOrientationLocked}');
InputImageRotation? rotation;
if (Platform.isIOS) {
rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
} else if (Platform.isAndroid) {
var rotationCompensation =
_orientations[_controller!.value.deviceOrientation];
if (rotationCompensation == null) return null;
if (camera.lensDirection == CameraLensDirection.front) {
// front-facing
rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
} else {
// back-facing
rotationCompensation =
(sensorOrientation - rotationCompensation + 360) % 360;
}
rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
// print('rotationCompensation: $rotationCompensation');
}
if (rotation == null) return null;
// print('final rotation: $rotation');

// get image format
final format = InputImageFormatValue.fromRawValue(image.format.raw as int);
// validate format depending on platform
// only supported formats:
// * nv21 for Android
// * bgra8888 for iOS
if (format == null ||
(Platform.isAndroid && format != InputImageFormat.nv21) ||
(Platform.isIOS && format != InputImageFormat.bgra8888)) return null;

// since format is constraint to nv21 or bgra8888, both only have one plane
if (image.planes.length != 1) return null;
final plane = image.planes.first;

// compose InputImage using bytes
return InputImage.fromBytes(
bytes: plane.bytes,
metadata: InputImageMetadata(
size: Size(image.width.toDouble(), image.height.toDouble()),
rotation: rotation, // used only in Android
format: format, // used only in iOS
bytesPerRow: plane.bytesPerRow, // used only in iOS
),
);
}
}

class SquarePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final double squareLength = 100;
final double borderWidth = 3;
final double borderRadius = 5;

final Offset center = size.center(Offset.zero);
final Rect squareRect = Rect.fromCenter(
center: center,
width: squareLength,
height: squareLength,
);

final Paint borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;

final RRect squareRRect =
RRect.fromRectAndRadius(squareRect, Radius.circular(borderRadius));

canvas.drawRRect(squareRRect, borderPaint);
}

@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
Loading

0 comments on commit d1ea233

Please sign in to comment.