Julian Steenbakker
Committed by GitHub

Merge pull request #40 from juliansteenbakker/image-picker

Feature: Image picker
## 0.2.0
You can provide a path to controller.analyzeImage(path) in order to scan a local photo from the gallery!
Check out the example app to see how you can use the image_picker plugin to retrieve a photo from
the gallery. Please keep in mind that this feature is only supported on Android and iOS.
## 0.1.3
* Fixed crash after asking permission. [#29](https://github.com/juliansteenbakker/mobile_scanner/issues/29)
* 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
| :-----: | :-: | :---: | :-: | :---: | :-----: |
| ✔️ | ✔️ | ✔️ | | | |
CameraX for Android requires at least SDK 21.
### Android
SDK 21 and newer. Reason: CameraX requires at least SDK 21.
MLKit for iOS requires at least iOS 11 and a [64bit device](https://developers.google.com/ml-kit/migration/ios).
### iOS
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).
# Usage
**Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:**
NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
**If you want to use the local gallery feature from [image_picker](https://pub.dev/packages/image_picker)**
NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor.
### macOS
macOS 10.13 or newer. Reason: Apple Vision library.
## Features Supported
| Features | Android | iOS | macOS | Web |
|------------------------|--------------------|--------------------|-------|-----|
| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
## Usage
Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller.
... ...
... ... @@ -4,6 +4,7 @@ import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.graphics.Point
import android.net.Uri
import android.util.Log
import android.util.Size
import android.view.Surface
... ... @@ -22,6 +23,8 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.view.TextureRegistry
import java.io.File
import java.net.URI
class MobileScanner(private val activity: Activity, private val textureRegistry: TextureRegistry)
... ... @@ -50,6 +53,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
"torch" -> toggleTorch(call, result)
// "analyze" -> switchAnalyzeMode(call, result)
"stop" -> stop(result)
"analyzeImage" -> analyzeImage(call, result)
else -> result.notImplemented()
}
}
... ... @@ -205,6 +209,24 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
// result.success(null)
// }
private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
val uri = Uri.fromFile( File(call.arguments.toString()))
val inputImage = InputImage.fromFilePath(activity, uri)
var barcodeFound = false
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
barcodeFound = true
sink?.success(mapOf("name" to "barcode", "data" to barcode.data))
}
}
.addOnFailureListener { e -> Log.e(TAG, e.message, e)
result.error(TAG, e.message, e)}
.addOnCompleteListener { result.success(barcodeFound) }
}
private fun stop(result: MethodChannel.Result) {
if (camera == null) {
result.error(TAG,"Called stop() while already stopped!", null)
... ...
... ... @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access in order to open photos of barcodes</string>
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan barcodes</string>
<key>CFBundleDevelopmentRegion</key>
... ...
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerWithController extends StatefulWidget {
... ... @@ -85,7 +86,7 @@ class _BarcodeScannerWithControllerState
})),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 160,
width: MediaQuery.of(context).size.width - 200,
height: 50,
child: FittedBox(
child: Text(
... ... @@ -115,6 +116,32 @@ class _BarcodeScannerWithControllerState
iconSize: 32.0,
onPressed: () => controller.switchCamera(),
),
IconButton(
color: Colors.white,
icon: const Icon(Icons.image),
iconSize: 32.0,
onPressed: () async {
final ImagePicker _picker = ImagePicker();
// Pick an image
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery);
if (image != null) {
if (await controller.analyzeImage(image.path)) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Barcode found!'),
backgroundColor: Colors.green,
));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('No barcode found!'),
backgroundColor: Colors.red,
));
}
}
},
),
],
),
),
... ...
... ... @@ -6,6 +6,7 @@ environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
image_picker: ^0.8.4+9
flutter:
sdk: flutter
... ...
... ... @@ -22,11 +22,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
// Image to be sent to the texture
var latestBuffer: CVImageBuffer!
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
let scanner = BarcodeScanner.barcodeScanner()
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftMobileScannerPlugin(registrar.textures())
... ... @@ -58,6 +59,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
// switchAnalyzeMode(call, result)
case "stop":
stop(result)
case "analyzeImage":
analyzeImage(call, result)
default:
result(FlutterMethodNotImplemented)
}
... ... @@ -102,7 +105,6 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
defaultOrientation: .portrait
)
let scanner = BarcodeScanner.barcodeScanner()
scanner.process(image) { [self] barcodes, error in
if error == nil && barcodes != nil {
for barcode in barcodes! {
... ... @@ -255,6 +257,42 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
// result(nil)
// }
func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let uiImage = UIImage(contentsOfFile: call.arguments as! String)
if (uiImage == nil) {
result(FlutterError(code: "MobileScanner",
message: "No image found in analyzeImage!",
details: nil))
return
}
let image = VisionImage(image: uiImage!)
image.orientation = imageOrientation(
deviceOrientation: UIDevice.current.orientation,
defaultOrientation: .portrait
)
var barcodeFound = false
scanner.process(image) { [self] barcodes, error in
if error == nil && barcodes != nil {
for barcode in barcodes! {
barcodeFound = true
let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
sink?(event)
}
} else if error != nil {
result(FlutterError(code: "MobileScanner",
message: error?.localizedDescription,
details: "analyzeImage()"))
}
analyzing = false
result(barcodeFound)
}
}
func stop(_ result: FlutterResult) {
if (device == nil) {
result(FlutterError(code: "MobileScanner",
... ...
... ... @@ -208,7 +208,16 @@ class MobileScannerController {
await start();
}
/// Disposes the controller and closes all listeners.
/// Handles a local image file.
/// Returns true if a barcode or QR code is found.
/// Returns false if nothing is found.
///
/// [path] The path of the image on the devices
Future<bool> analyzeImage(String path) async {
return await methodChannel.invokeMethod('analyzeImage', path);
}
/// Disposes the MobileScannerController and closes all listeners.
void dispose() {
if (hashCode == _controllerHashcode) {
stop();
... ... @@ -219,11 +228,11 @@ class MobileScannerController {
barcodesController.close();
}
/// Checks if the controller is bound to the correct MobileScanner object.
/// Checks if the MobileScannerController is bound to the correct MobileScanner object.
void ensure(String name) {
final message =
'CameraController.$name called after CameraController.dispose\n'
'CameraController methods should not be used after calling dispose.';
'MobileScannerController.$name called after MobileScannerController.dispose\n'
'MobileScannerController methods should not be used after calling dispose.';
assert(hashCode == _controllerHashcode, message);
}
}
... ...
name: mobile_scanner
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.
version: 0.1.3
version: 0.2.0
repository: https://github.com/juliansteenbakker/mobile_scanner
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=2.2.0"
dependencies:
flutter:
... ...