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

338 строки
10 KiB

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