Julian Steenbakker
Committed by GitHub

Merge pull request #40 from juliansteenbakker/image-picker

Feature: Image picker
  1 +## 0.2.0
  2 +You can provide a path to controller.analyzeImage(path) in order to scan a local photo from the gallery!
  3 +Check out the example app to see how you can use the image_picker plugin to retrieve a photo from
  4 +the gallery. Please keep in mind that this feature is only supported on Android and iOS.
  5 +
1 ## 0.1.3 6 ## 0.1.3
2 * Fixed crash after asking permission. [#29](https://github.com/juliansteenbakker/mobile_scanner/issues/29) 7 * Fixed crash after asking permission. [#29](https://github.com/juliansteenbakker/mobile_scanner/issues/29)
3 * Upgraded cameraX from 1.1.0-beta01 to 1.1.0-beta02 8 * Upgraded cameraX from 1.1.0-beta01 to 1.1.0-beta02
@@ -11,11 +11,30 @@ A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX @@ -11,11 +11,30 @@ A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX
11 | :-----: | :-: | :---: | :-: | :---: | :-----: | 11 | :-----: | :-: | :---: | :-: | :---: | :-----: |
12 | ✔️ | ✔️ | ✔️ | | | | 12 | ✔️ | ✔️ | ✔️ | | | |
13 13
14 -CameraX for Android requires at least SDK 21. 14 +### Android
  15 +SDK 21 and newer. Reason: CameraX requires at least SDK 21.
15 16
16 -MLKit for iOS requires at least iOS 11 and a [64bit device](https://developers.google.com/ml-kit/migration/ios). 17 +### iOS
  18 +iOS 11 and newer. Reason: MLKit for iOS requires at least iOS 11 and a [64bit device](https://developers.google.com/ml-kit/migration/ios).
17 19
18 -# Usage 20 +**Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:**
  21 +
  22 +NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
  23 +
  24 +**If you want to use the local gallery feature from [image_picker](https://pub.dev/packages/image_picker)**
  25 +
  26 +NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor.
  27 +
  28 +### macOS
  29 +macOS 10.13 or newer. Reason: Apple Vision library.
  30 +
  31 +## Features Supported
  32 +
  33 +| Features | Android | iOS | macOS | Web |
  34 +|------------------------|--------------------|--------------------|-------|-----|
  35 +| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
  36 +
  37 +## Usage
19 38
20 Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller. 39 Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller.
21 40
@@ -4,6 +4,7 @@ import android.Manifest @@ -4,6 +4,7 @@ import android.Manifest
4 import android.app.Activity 4 import android.app.Activity
5 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
6 import android.graphics.Point 6 import android.graphics.Point
  7 +import android.net.Uri
7 import android.util.Log 8 import android.util.Log
8 import android.util.Size 9 import android.util.Size
9 import android.view.Surface 10 import android.view.Surface
@@ -22,6 +23,8 @@ import io.flutter.plugin.common.MethodCall @@ -22,6 +23,8 @@ import io.flutter.plugin.common.MethodCall
22 import io.flutter.plugin.common.MethodChannel 23 import io.flutter.plugin.common.MethodChannel
23 import io.flutter.plugin.common.PluginRegistry 24 import io.flutter.plugin.common.PluginRegistry
24 import io.flutter.view.TextureRegistry 25 import io.flutter.view.TextureRegistry
  26 +import java.io.File
  27 +import java.net.URI
25 28
26 29
27 class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry) 30 class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
@@ -50,6 +53,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -50,6 +53,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
50 "torch" -> toggleTorch(call, result) 53 "torch" -> toggleTorch(call, result)
51 // "analyze" -> switchAnalyzeMode(call, result) 54 // "analyze" -> switchAnalyzeMode(call, result)
52 "stop" -> stop(result) 55 "stop" -> stop(result)
  56 + "analyzeImage" -> analyzeImage(call, result)
53 else -> result.notImplemented() 57 else -> result.notImplemented()
54 } 58 }
55 } 59 }
@@ -205,6 +209,24 @@ class MobileScanner(private val activity: Activity, private val textureRegistry: @@ -205,6 +209,24 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
205 // result.success(null) 209 // result.success(null)
206 // } 210 // }
207 211
  212 + private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
  213 + val uri = Uri.fromFile( File(call.arguments.toString()))
  214 + val inputImage = InputImage.fromFilePath(activity, uri)
  215 +
  216 + var barcodeFound = false
  217 + scanner.process(inputImage)
  218 + .addOnSuccessListener { barcodes ->
  219 + for (barcode in barcodes) {
  220 + barcodeFound = true
  221 + sink?.success(mapOf("name" to "barcode", "data" to barcode.data))
  222 + }
  223 + }
  224 + .addOnFailureListener { e -> Log.e(TAG, e.message, e)
  225 + result.error(TAG, e.message, e)}
  226 + .addOnCompleteListener { result.success(barcodeFound) }
  227 +
  228 + }
  229 +
208 private fun stop(result: MethodChannel.Result) { 230 private fun stop(result: MethodChannel.Result) {
209 if (camera == null) { 231 if (camera == null) {
210 result.error(TAG,"Called stop() while already stopped!", null) 232 result.error(TAG,"Called stop() while already stopped!", null)
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0"> 3 <plist version="1.0">
4 <dict> 4 <dict>
  5 + <key>NSPhotoLibraryUsageDescription</key>
  6 + <string>We need access in order to open photos of barcodes</string>
5 <key>NSCameraUsageDescription</key> 7 <key>NSCameraUsageDescription</key>
6 <string>We use the camera to scan barcodes</string> 8 <string>We use the camera to scan barcodes</string>
7 <key>CFBundleDevelopmentRegion</key> 9 <key>CFBundleDevelopmentRegion</key>
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
  2 +import 'package:image_picker/image_picker.dart';
2 import 'package:mobile_scanner/mobile_scanner.dart'; 3 import 'package:mobile_scanner/mobile_scanner.dart';
3 4
4 class BarcodeScannerWithController extends StatefulWidget { 5 class BarcodeScannerWithController extends StatefulWidget {
@@ -85,7 +86,7 @@ class _BarcodeScannerWithControllerState @@ -85,7 +86,7 @@ class _BarcodeScannerWithControllerState
85 })), 86 })),
86 Center( 87 Center(
87 child: SizedBox( 88 child: SizedBox(
88 - width: MediaQuery.of(context).size.width - 160, 89 + width: MediaQuery.of(context).size.width - 200,
89 height: 50, 90 height: 50,
90 child: FittedBox( 91 child: FittedBox(
91 child: Text( 92 child: Text(
@@ -115,6 +116,32 @@ class _BarcodeScannerWithControllerState @@ -115,6 +116,32 @@ class _BarcodeScannerWithControllerState
115 iconSize: 32.0, 116 iconSize: 32.0,
116 onPressed: () => controller.switchCamera(), 117 onPressed: () => controller.switchCamera(),
117 ), 118 ),
  119 + IconButton(
  120 + color: Colors.white,
  121 + icon: const Icon(Icons.image),
  122 + iconSize: 32.0,
  123 + onPressed: () async {
  124 + final ImagePicker _picker = ImagePicker();
  125 + // Pick an image
  126 + final XFile? image = await _picker.pickImage(
  127 + source: ImageSource.gallery);
  128 + if (image != null) {
  129 + if (await controller.analyzeImage(image.path)) {
  130 + ScaffoldMessenger.of(context)
  131 + .showSnackBar(const SnackBar(
  132 + content: Text('Barcode found!'),
  133 + backgroundColor: Colors.green,
  134 + ));
  135 + } else {
  136 + ScaffoldMessenger.of(context)
  137 + .showSnackBar(const SnackBar(
  138 + content: Text('No barcode found!'),
  139 + backgroundColor: Colors.red,
  140 + ));
  141 + }
  142 + }
  143 + },
  144 + ),
118 ], 145 ],
119 ), 146 ),
120 ), 147 ),
@@ -6,6 +6,7 @@ environment: @@ -6,6 +6,7 @@ environment:
6 sdk: ">=2.12.0 <3.0.0" 6 sdk: ">=2.12.0 <3.0.0"
7 7
8 dependencies: 8 dependencies:
  9 + image_picker: ^0.8.4+9
9 flutter: 10 flutter:
10 sdk: flutter 11 sdk: flutter
11 12
@@ -22,11 +22,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -22,11 +22,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
22 // Image to be sent to the texture 22 // Image to be sent to the texture
23 var latestBuffer: CVImageBuffer! 23 var latestBuffer: CVImageBuffer!
24 24
25 -  
26 // var analyzeMode: Int = 0 25 // var analyzeMode: Int = 0
27 var analyzing: Bool = false 26 var analyzing: Bool = false
28 var position = AVCaptureDevice.Position.back 27 var position = AVCaptureDevice.Position.back
29 28
  29 + let scanner = BarcodeScanner.barcodeScanner()
  30 +
30 public static func register(with registrar: FlutterPluginRegistrar) { 31 public static func register(with registrar: FlutterPluginRegistrar) {
31 let instance = SwiftMobileScannerPlugin(registrar.textures()) 32 let instance = SwiftMobileScannerPlugin(registrar.textures())
32 33
@@ -58,6 +59,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -58,6 +59,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
58 // switchAnalyzeMode(call, result) 59 // switchAnalyzeMode(call, result)
59 case "stop": 60 case "stop":
60 stop(result) 61 stop(result)
  62 + case "analyzeImage":
  63 + analyzeImage(call, result)
61 default: 64 default:
62 result(FlutterMethodNotImplemented) 65 result(FlutterMethodNotImplemented)
63 } 66 }
@@ -102,7 +105,6 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -102,7 +105,6 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
102 defaultOrientation: .portrait 105 defaultOrientation: .portrait
103 ) 106 )
104 107
105 - let scanner = BarcodeScanner.barcodeScanner()  
106 scanner.process(image) { [self] barcodes, error in 108 scanner.process(image) { [self] barcodes, error in
107 if error == nil && barcodes != nil { 109 if error == nil && barcodes != nil {
108 for barcode in barcodes! { 110 for barcode in barcodes! {
@@ -255,6 +257,42 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -255,6 +257,42 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
255 // result(nil) 257 // result(nil)
256 // } 258 // }
257 259
  260 + func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  261 + let uiImage = UIImage(contentsOfFile: call.arguments as! String)
  262 +
  263 + if (uiImage == nil) {
  264 + result(FlutterError(code: "MobileScanner",
  265 + message: "No image found in analyzeImage!",
  266 + details: nil))
  267 + return
  268 + }
  269 +
  270 + let image = VisionImage(image: uiImage!)
  271 + image.orientation = imageOrientation(
  272 + deviceOrientation: UIDevice.current.orientation,
  273 + defaultOrientation: .portrait
  274 + )
  275 +
  276 + var barcodeFound = false
  277 +
  278 + scanner.process(image) { [self] barcodes, error in
  279 + if error == nil && barcodes != nil {
  280 + for barcode in barcodes! {
  281 + barcodeFound = true
  282 + let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
  283 + sink?(event)
  284 + }
  285 + } else if error != nil {
  286 + result(FlutterError(code: "MobileScanner",
  287 + message: error?.localizedDescription,
  288 + details: "analyzeImage()"))
  289 + }
  290 + analyzing = false
  291 + result(barcodeFound)
  292 + }
  293 +
  294 + }
  295 +
258 func stop(_ result: FlutterResult) { 296 func stop(_ result: FlutterResult) {
259 if (device == nil) { 297 if (device == nil) {
260 result(FlutterError(code: "MobileScanner", 298 result(FlutterError(code: "MobileScanner",
@@ -208,7 +208,16 @@ class MobileScannerController { @@ -208,7 +208,16 @@ class MobileScannerController {
208 await start(); 208 await start();
209 } 209 }
210 210
211 - /// Disposes the controller and closes all listeners. 211 + /// Handles a local image file.
  212 + /// Returns true if a barcode or QR code is found.
  213 + /// Returns false if nothing is found.
  214 + ///
  215 + /// [path] The path of the image on the devices
  216 + Future<bool> analyzeImage(String path) async {
  217 + return await methodChannel.invokeMethod('analyzeImage', path);
  218 + }
  219 +
  220 + /// Disposes the MobileScannerController and closes all listeners.
212 void dispose() { 221 void dispose() {
213 if (hashCode == _controllerHashcode) { 222 if (hashCode == _controllerHashcode) {
214 stop(); 223 stop();
@@ -219,11 +228,11 @@ class MobileScannerController { @@ -219,11 +228,11 @@ class MobileScannerController {
219 barcodesController.close(); 228 barcodesController.close();
220 } 229 }
221 230
222 - /// Checks if the controller is bound to the correct MobileScanner object. 231 + /// Checks if the MobileScannerController is bound to the correct MobileScanner object.
223 void ensure(String name) { 232 void ensure(String name) {
224 final message = 233 final message =
225 - 'CameraController.$name called after CameraController.dispose\n'  
226 - 'CameraController methods should not be used after calling dispose.'; 234 + 'MobileScannerController.$name called after MobileScannerController.dispose\n'
  235 + 'MobileScannerController methods should not be used after calling dispose.';
227 assert(hashCode == _controllerHashcode, message); 236 assert(hashCode == _controllerHashcode, message);
228 } 237 }
229 } 238 }
1 name: mobile_scanner 1 name: mobile_scanner
2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. 2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
3 -version: 0.1.3 3 +version: 0.2.0
4 repository: https://github.com/juliansteenbakker/mobile_scanner 4 repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 environment: 6 environment:
7 sdk: ">=2.12.0 <3.0.0" 7 sdk: ">=2.12.0 <3.0.0"
8 - flutter: ">=2.2.0"  
9 8
10 dependencies: 9 dependencies:
11 flutter: 10 flutter: