The QR Code Scanner package for Appeto SDK.
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

357 строки
11 KiB

  1. import 'dart:async';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'lifecycle_event_handler.dart';
  6. import 'qr_scanner_overlay_shape.dart';
  7. import 'types/barcode.dart';
  8. import 'types/barcode_format.dart';
  9. import 'types/camera.dart';
  10. import 'types/camera_exception.dart';
  11. import 'types/features.dart';
  12. import 'web/flutter_qr_stub.dart'
  13. // ignore: uri_does_not_exist
  14. if (dart.library.html) 'web/flutter_qr_web.dart';
  15. typedef QRViewCreatedCallback = void Function(QRViewController);
  16. typedef PermissionSetCallback = void Function(QRViewController, bool);
  17. /// The [QRView] is the view where the camera
  18. /// and the barcode scanner gets displayed.
  19. class QRView extends StatefulWidget {
  20. const QRView({
  21. required Key key,
  22. required this.onQRViewCreated,
  23. this.overlay,
  24. this.overlayMargin = EdgeInsets.zero,
  25. this.cameraFacing = CameraFacing.back,
  26. this.onPermissionSet,
  27. this.formatsAllowed = const <BarcodeFormat>[],
  28. }) : super(key: key);
  29. /// [onQRViewCreated] gets called when the view is created
  30. final QRViewCreatedCallback onQRViewCreated;
  31. /// Use [overlay] to provide an overlay for the view.
  32. /// This can be used to create a certain scan area.
  33. final QrScannerOverlayShape? overlay;
  34. /// Use [overlayMargin] to provide a margin to [overlay]
  35. final EdgeInsetsGeometry overlayMargin;
  36. /// Set which camera to use on startup.
  37. ///
  38. /// [cameraFacing] can either be CameraFacing.front or CameraFacing.back.
  39. /// Defaults to CameraFacing.back
  40. final CameraFacing cameraFacing;
  41. /// Calls the provided [onPermissionSet] callback when the permission is set.
  42. final PermissionSetCallback? onPermissionSet;
  43. /// Use [formatsAllowed] to specify which formats needs to be scanned.
  44. final List<BarcodeFormat> formatsAllowed;
  45. @override
  46. State<StatefulWidget> createState() => _QRViewState();
  47. }
  48. class _QRViewState extends State<QRView> {
  49. late MethodChannel _channel;
  50. late LifecycleEventHandler _observer;
  51. @override
  52. void initState() {
  53. super.initState();
  54. _observer = LifecycleEventHandler(resumeCallBack: updateDimensions);
  55. WidgetsBinding.instance!.addObserver(_observer);
  56. }
  57. @override
  58. Widget build(BuildContext context) {
  59. return NotificationListener(
  60. onNotification: onNotification,
  61. child: SizeChangedLayoutNotifier(
  62. child: (widget.overlay != null)
  63. ? _getPlatformQrViewWithOverlay()
  64. : _getPlatformQrView(),
  65. ),
  66. );
  67. }
  68. @override
  69. void dispose() {
  70. super.dispose();
  71. WidgetsBinding.instance!.removeObserver(_observer);
  72. }
  73. Future<void> updateDimensions() async {
  74. await QRViewController.updateDimensions(
  75. widget.key as GlobalKey<State<StatefulWidget>>, _channel,
  76. overlay: widget.overlay);
  77. }
  78. bool onNotification(notification) {
  79. updateDimensions();
  80. return false;
  81. }
  82. Widget _getPlatformQrViewWithOverlay() {
  83. return Stack(
  84. children: [
  85. _getPlatformQrView(),
  86. Container(
  87. padding: widget.overlayMargin,
  88. decoration: ShapeDecoration(
  89. shape: widget.overlay!,
  90. ),
  91. )
  92. ],
  93. );
  94. }
  95. Widget _getPlatformQrView() {
  96. Widget _platformQrView;
  97. if (kIsWeb) {
  98. _platformQrView = createWebQrView(
  99. onPlatformViewCreated: widget.onQRViewCreated,
  100. cameraFacing: widget.cameraFacing,
  101. );
  102. } else {
  103. switch (defaultTargetPlatform) {
  104. case TargetPlatform.android:
  105. _platformQrView = AndroidView(
  106. viewType: 'net.touchcapture.qr.flutterqr/qrview',
  107. onPlatformViewCreated: _onPlatformViewCreated,
  108. creationParams:
  109. _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(),
  110. creationParamsCodec: StandardMessageCodec(),
  111. );
  112. break;
  113. case TargetPlatform.iOS:
  114. _platformQrView = UiKitView(
  115. viewType: 'net.touchcapture.qr.flutterqr/qrview',
  116. onPlatformViewCreated: _onPlatformViewCreated,
  117. creationParams:
  118. _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(),
  119. creationParamsCodec: StandardMessageCodec(),
  120. );
  121. break;
  122. default:
  123. throw UnsupportedError(
  124. "Trying to use the default qrview implementation for $defaultTargetPlatform but there isn't a default one");
  125. }
  126. }
  127. return _platformQrView;
  128. }
  129. void _onPlatformViewCreated(int id) {
  130. _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id');
  131. // Start scan after creation of the view
  132. final controller = QRViewController._(
  133. _channel,
  134. widget.key as GlobalKey<State<StatefulWidget>>?,
  135. widget.onPermissionSet,
  136. widget.cameraFacing)
  137. .._startScan(widget.key as GlobalKey<State<StatefulWidget>>,
  138. widget.overlay, widget.formatsAllowed);
  139. // Initialize the controller for controlling the QRView
  140. widget.onQRViewCreated(controller);
  141. }
  142. }
  143. class _QrCameraSettings {
  144. _QrCameraSettings({
  145. this.cameraFacing = CameraFacing.unknown,
  146. });
  147. final CameraFacing cameraFacing;
  148. Map<String, dynamic> toMap() {
  149. return <String, dynamic>{
  150. 'cameraFacing': cameraFacing.index,
  151. };
  152. }
  153. }
  154. class QRViewController {
  155. QRViewController._(MethodChannel channel, GlobalKey? qrKey,
  156. PermissionSetCallback? onPermissionSet, CameraFacing cameraFacing)
  157. : _channel = channel,
  158. _cameraFacing = cameraFacing {
  159. _channel.setMethodCallHandler((call) async {
  160. switch (call.method) {
  161. case 'onRecognizeQR':
  162. if (call.arguments != null) {
  163. final args = call.arguments as Map;
  164. final code = args['code'] as String;
  165. final rawType = args['type'] as String;
  166. // Raw bytes are only supported by Android.
  167. final rawBytes = args['rawBytes'] as List<int>?;
  168. final format = BarcodeTypesExtension.fromString(rawType);
  169. if (format != BarcodeFormat.unknown) {
  170. final barcode = Barcode(code, format, rawBytes);
  171. _scanUpdateController.sink.add(barcode);
  172. } else {
  173. throw Exception('Unexpected barcode type $rawType');
  174. }
  175. }
  176. break;
  177. case 'onPermissionSet':
  178. if (call.arguments != null && call.arguments is bool) {
  179. _hasPermissions = call.arguments;
  180. if (onPermissionSet != null) {
  181. onPermissionSet(this, _hasPermissions);
  182. }
  183. }
  184. break;
  185. }
  186. });
  187. }
  188. final MethodChannel _channel;
  189. final CameraFacing _cameraFacing;
  190. final StreamController<Barcode> _scanUpdateController =
  191. StreamController<Barcode>();
  192. Stream<Barcode> get scannedDataStream => _scanUpdateController.stream;
  193. bool _hasPermissions = false;
  194. bool get hasPermissions => _hasPermissions;
  195. /// Starts the barcode scanner
  196. Future<void> _startScan(GlobalKey key, QrScannerOverlayShape? overlay,
  197. List<BarcodeFormat>? barcodeFormats) async {
  198. // We need to update the dimension before the scan is started.
  199. try {
  200. await QRViewController.updateDimensions(key, _channel, overlay: overlay);
  201. return await _channel.invokeMethod(
  202. 'startScan', barcodeFormats?.map((e) => e.asInt()).toList() ?? []);
  203. } on PlatformException catch (e) {
  204. throw CameraException(e.code, e.message);
  205. }
  206. }
  207. /// Gets information about which camera is active.
  208. Future<CameraFacing> getCameraInfo() async {
  209. try {
  210. var cameraFacing = await _channel.invokeMethod('getCameraInfo') as int;
  211. if (cameraFacing == -1) return _cameraFacing;
  212. return CameraFacing
  213. .values[await _channel.invokeMethod('getCameraInfo') as int];
  214. } on PlatformException catch (e) {
  215. throw CameraException(e.code, e.message);
  216. }
  217. }
  218. /// Flips the camera between available modes
  219. Future<CameraFacing> flipCamera() async {
  220. try {
  221. return CameraFacing
  222. .values[await _channel.invokeMethod('flipCamera') as int];
  223. } on PlatformException catch (e) {
  224. throw CameraException(e.code, e.message);
  225. }
  226. }
  227. /// Get flashlight status
  228. Future<bool?> getFlashStatus() async {
  229. try {
  230. return await _channel.invokeMethod('getFlashInfo');
  231. } on PlatformException catch (e) {
  232. throw CameraException(e.code, e.message);
  233. }
  234. }
  235. /// Toggles the flashlight between available modes
  236. Future<void> toggleFlash() async {
  237. try {
  238. await _channel.invokeMethod('toggleFlash') as bool?;
  239. } on PlatformException catch (e) {
  240. throw CameraException(e.code, e.message);
  241. }
  242. }
  243. /// Pauses the camera and barcode scanning
  244. Future<void> pauseCamera() async {
  245. try {
  246. await _channel.invokeMethod('pauseCamera');
  247. } on PlatformException catch (e) {
  248. throw CameraException(e.code, e.message);
  249. }
  250. }
  251. /// Stops barcode scanning and the camera
  252. Future<void> stopCamera() async {
  253. try {
  254. await _channel.invokeMethod('stopCamera');
  255. } on PlatformException catch (e) {
  256. throw CameraException(e.code, e.message);
  257. }
  258. }
  259. /// Resumes barcode scanning
  260. Future<void> resumeCamera() async {
  261. try {
  262. await _channel.invokeMethod('resumeCamera');
  263. } on PlatformException catch (e) {
  264. throw CameraException(e.code, e.message);
  265. }
  266. }
  267. /// Returns which features are available on device.
  268. Future<SystemFeatures> getSystemFeatures() async {
  269. try {
  270. var features =
  271. await _channel.invokeMapMethod<String, dynamic>('getSystemFeatures');
  272. if (features != null) {
  273. return SystemFeatures.fromJson(features);
  274. }
  275. throw CameraException('Error', 'Could not get system features');
  276. } on PlatformException catch (e) {
  277. throw CameraException(e.code, e.message);
  278. }
  279. }
  280. /// Stops the camera and disposes the barcode stream.
  281. void dispose() {
  282. if (defaultTargetPlatform == TargetPlatform.iOS) stopCamera();
  283. _scanUpdateController.close();
  284. }
  285. /// Updates the view dimensions for iOS.
  286. static Future<bool> updateDimensions(GlobalKey key, MethodChannel channel,
  287. {QrScannerOverlayShape? overlay}) async {
  288. if (defaultTargetPlatform == TargetPlatform.iOS) {
  289. // Add small delay to ensure the render box is loaded
  290. await Future.delayed(Duration(milliseconds: 300));
  291. if (key.currentContext == null) return false;
  292. final renderBox = key.currentContext!.findRenderObject() as RenderBox;
  293. try {
  294. await channel.invokeMethod('setDimensions', {
  295. 'width': renderBox.size.width,
  296. 'height': renderBox.size.height,
  297. 'scanArea': overlay?.cutOutSize ?? 0,
  298. 'scanAreaOffset': overlay?.cutOutBottomOffset ?? 0
  299. });
  300. return true;
  301. } on PlatformException catch (e) {
  302. throw CameraException(e.code, e.message);
  303. }
  304. } else if (defaultTargetPlatform == TargetPlatform.android) {
  305. if (overlay == null) {
  306. return false;
  307. }
  308. await channel.invokeMethod('changeScanArea', {
  309. 'cutOutSize': overlay.cutOutSize,
  310. 'cutOutBottomOffset': overlay.cutOutBottomOffset
  311. });
  312. return true;
  313. }
  314. return false;
  315. }
  316. }