Julian Steenbakker
Committed by GitHub

Merge branch 'master' into master

# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# review when someone opens a pull request.
# For more on how to customize the CODEOWNERS file - https://help.github.com/en/articles/about-code-owners
* @juliansteenbakker
... ...
... ... @@ -6,15 +6,33 @@ updates:
interval: "weekly"
reviewers:
- "juliansteenbakker"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: gradle
directory: "/android"
schedule:
interval: "weekly"
reviewers:
- "juliansteenbakker"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: gradle
directory: "/example/android"
schedule:
interval: "weekly"
reviewers:
- "juliansteenbakker"
\ No newline at end of file
- "juliansteenbakker"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: "pub"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "chore"
include: "scope"
reviewers:
- "juliansteenbakker"
... ...
# .github/workflows/auto-author-assign.yml
name: 'Auto Author Assign'
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
jobs:
assign-author:
runs-on: ubuntu-latest
steps:
- uses: toshimaru/auto-author-assign@v1.6.1
... ...
name: code analysis & formatting
on: [push, pull_request]
on:
push:
branches:
- master
pull_request:
types: [ opened, labeled, unlabeled, synchronize ]
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- uses: actions/checkout@v3.0.2
- uses: actions/setup-java@v3.4.1
with:
distribution: 'temurin'
java-version: '11'
- uses: subosito/flutter-action@v2.4.0
java-version: 11
distribution: temurin
- uses: subosito/flutter-action@v2.6.1
with:
cache: true
- name: Version
run: flutter doctor -v
- name: Install dependencies
... ... @@ -21,11 +28,13 @@ jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- uses: actions/checkout@v3.0.2
- uses: actions/setup-java@v3.4.1
with:
java-version: 11
distribution: temurin
- uses: subosito/flutter-action@v2.6.1
with:
distribution: 'temurin'
java-version: '11'
- uses: subosito/flutter-action@v2.4.0
cache: true
- name: Format
run: flutter format -n --set-exit-if-changed .
\ No newline at end of file
run: flutter format -n --set-exit-if-changed .
... ...
name: Run release-please
on:
push:
branches:
- master
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v3.5.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
release-type: simple
... ...
name: "Semantic PRs"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
... ...
## 3.0.0
Breaking changes:
* [Android] SDK updated to SDK 33.
Other changes:
* [Android] Revert camera2 dependency to stable release
* [iOS] Update barcode scanning library to latest version
## 2.0.0
Breaking changes:
This version is only compatible with flutter 3.0.0 and later.
... ...
... ... @@ -169,3 +169,41 @@ import 'package:mobile_scanner/mobile_scanner.dart';
}));
}
```
Example with controller and returning images
```dart
import 'package:mobile_scanner/mobile_scanner.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Mobile Scanner')),
body: MobileScanner(
controller: MobileScannerController(
facing: CameraFacing.front,
torchEnabled: true,
returnImage: true,
),
onDetect: (barcode, args) {
if (barcode.rawValue == null) {
debugPrint('Failed to scan Barcode');
} else {
final String code = barcode.rawValue!;
debugPrint('Barcode found! $code');
debugPrint(
'Image returned! length: ${barcode.image!.lengthInBytes}b');
showDialog(
context: context,
builder: (context) => Image(image: MemoryImage(barcode.image!)),
);
Future.delayed(const Duration(seconds: 2), () {
Navigator.pop(context);
});
}
},
),
);
}
```
\ No newline at end of file
... ...
... ... @@ -2,14 +2,14 @@ group 'dev.steenbakker.mobile_scanner'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
}
}
... ... @@ -24,7 +24,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 32
compileSdkVersion 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
... ... @@ -52,16 +52,6 @@ dependencies {
// Use this dependency to use the dynamically downloaded model in Google Play Services
// implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.0.0'
implementation "androidx.camera:camera-camera2:1.2.0-alpha01"
implementation 'androidx.camera:camera-lifecycle:1.2.0-alpha01'
// // The following line is optional, as the core library is included indirectly by camera-camera2
// implementation "androidx.camera:camera-core:1.1.0-alpha11"
// implementation "androidx.camera:camera-camera2:1.1.0-alpha11"
// // If you want to additionally use the CameraX Lifecycle library
// implementation "androidx.camera:camera-lifecycle:1.1.0-alpha11"
// // If you want to additionally use the CameraX View class
// implementation "androidx.camera:camera-view:1.0.0-alpha31"
// // If you want to additionally use the CameraX Extensions library
// implementation "androidx.camera:camera-extensions:1.0.0-alpha31"
implementation 'androidx.camera:camera-camera2:1.1.0'
implementation 'androidx.camera:camera-lifecycle:1.1.0'
}
... ...
... ... @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 32
compileSdkVersion 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
... ... @@ -45,7 +45,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.steenbakker.mobile_scanner_example"
minSdkVersion 21
targetSdkVersion 32
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
... ...
buildscript {
ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
... ...
#Tue May 31 10:34:01 CEST 2022
#Tue Aug 23 15:51:00 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
... ...
... ... @@ -13,7 +13,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
C80F46710D9B9F4F17AD4E3D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E133769572782C32D37D8AC /* Pods_Runner.framework */; };
A5A2C2B73A9F26060DE9FB22 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54E006799E73DEAB41FD3623 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
... ... @@ -32,9 +32,9 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1CD9C88F6BFEF6CB7CA6746B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
32FD382A786B3A0080FE63FD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
5E133769572782C32D37D8AC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54E006799E73DEAB41FD3623 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
... ... @@ -45,8 +45,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E29A089CD1D61281C49DBB79 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
E33BE6AC5C06F7A45470ADE0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
D5B36FCD262B39F867CFDEEE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
F0D5742F0690BE32D07B033A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
... ... @@ -54,27 +54,19 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C80F46710D9B9F4F17AD4E3D /* Pods_Runner.framework in Frameworks */,
A5A2C2B73A9F26060DE9FB22 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0F766276E0F46921DEBF581B /* Frameworks */ = {
isa = PBXGroup;
children = (
5E133769572782C32D37D8AC /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
203D5C95A734778D93D18369 /* Pods */ = {
isa = PBXGroup;
children = (
E33BE6AC5C06F7A45470ADE0 /* Pods-Runner.debug.xcconfig */,
1CD9C88F6BFEF6CB7CA6746B /* Pods-Runner.release.xcconfig */,
E29A089CD1D61281C49DBB79 /* Pods-Runner.profile.xcconfig */,
D5B36FCD262B39F867CFDEEE /* Pods-Runner.debug.xcconfig */,
32FD382A786B3A0080FE63FD /* Pods-Runner.release.xcconfig */,
F0D5742F0690BE32D07B033A /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
... ... @@ -97,7 +89,7 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
203D5C95A734778D93D18369 /* Pods */,
0F766276E0F46921DEBF581B /* Frameworks */,
FF36E403CAC9E06A5A96BB9F /* Frameworks */,
);
sourceTree = "<group>";
};
... ... @@ -124,6 +116,14 @@
path = Runner;
sourceTree = "<group>";
};
FF36E403CAC9E06A5A96BB9F /* Frameworks */ = {
isa = PBXGroup;
children = (
54E006799E73DEAB41FD3623 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
... ... @@ -131,14 +131,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
1C759CA63421B131D22BB688 /* [CP] Check Pods Manifest.lock */,
B086C54F5791A4E759CB6822 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
EE97B31B239E017B5516C6AD /* [CP] Embed Pods Frameworks */,
F825A499E8C466DB9DC6247D /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
... ... @@ -197,57 +197,57 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
1C759CA63421B131D22BB688 /* [CP] Check Pods Manifest.lock */ = {
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "Thin Binary";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
B086C54F5791A4E759CB6822 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "Run Script";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EE97B31B239E017B5516C6AD /* [CP] Embed Pods Frameworks */ = {
F825A499E8C466DB9DC6247D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
... ... @@ -355,14 +355,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 75Y2P2WSQQ;
DEVELOPMENT_TEAM = RCH2VG82SH;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
... ... @@ -484,14 +484,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 75Y2P2WSQQ;
DEVELOPMENT_TEAM = RCH2VG82SH;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
... ... @@ -507,14 +507,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 75Y2P2WSQQ;
DEVELOPMENT_TEAM = RCH2VG82SH;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScannerExample;
PRODUCT_BUNDLE_IDENTIFIER = dev.steenbakker.mobileScanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
... ...
... ... @@ -2,10 +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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
... ... @@ -28,6 +26,10 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>We use the camera to scan barcodes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access in order to open photos of barcodes</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
... ... @@ -47,7 +49,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
... ...
... ... @@ -133,9 +133,9 @@ class _BarcodeScannerWithControllerState
icon: const Icon(Icons.image),
iconSize: 32.0,
onPressed: () async {
final ImagePicker _picker = ImagePicker();
final ImagePicker picker = ImagePicker();
// Pick an image
final XFile? image = await _picker.pickImage(
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
);
if (image != null) {
... ...
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerReturningImage extends StatefulWidget {
const BarcodeScannerReturningImage({Key? key}) : super(key: key);
@override
_BarcodeScannerReturningImageState createState() =>
_BarcodeScannerReturningImageState();
}
class _BarcodeScannerReturningImageState
extends State<BarcodeScannerReturningImage>
with SingleTickerProviderStateMixin {
String? barcode;
Uint8List? image;
MobileScannerController controller = MobileScannerController(
torchEnabled: true,
returnImage: true,
// formats: [BarcodeFormat.qrCode]
// facing: CameraFacing.front,
);
bool isStarted = true;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
return Stack(
children: [
MobileScanner(
controller: controller,
fit: BoxFit.contain,
// allowDuplicates: true,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect: (barcode, args) {
setState(() {
this.barcode = barcode.rawValue;
showDialog(
context: context,
builder: (context) => Image(
image: MemoryImage(image!),
fit: BoxFit.contain,
),
);
image = barcode.image;
});
},
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
if (state == null) {
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
}
switch (state as TorchState) {
case TorchState.off:
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
case TorchState.on:
return const Icon(
Icons.flash_on,
color: Colors.yellow,
);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
),
IconButton(
color: Colors.white,
icon: isStarted
? const Icon(Icons.stop)
: const Icon(Icons.play_arrow),
iconSize: 32.0,
onPressed: () => setState(() {
isStarted ? controller.stop() : controller.start();
isStarted = !isStarted;
}),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 200,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.cameraFacingState,
builder: (context, state, child) {
if (state == null) {
return const Icon(Icons.camera_front);
}
switch (state as CameraFacing) {
case CameraFacing.front:
return const Icon(Icons.camera_front);
case CameraFacing.back:
return const Icon(Icons.camera_rear);
}
},
),
iconSize: 32.0,
onPressed: () => controller.switchCamera(),
),
SizedBox(
width: 50,
height: 50,
child: image != null
? Image(
image: MemoryImage(image!),
fit: BoxFit.contain,
)
: Container(),
),
],
),
),
),
],
);
},
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
void main() => runApp(const MaterialApp(home: MyHome()));
... ... @@ -31,6 +32,17 @@ class MyHome extends StatelessWidget {
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const BarcodeScannerReturningImage(),
),
);
},
child:
const Text('MobileScanner with Controller (returning image)'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const BarcodeScannerWithoutController(),
),
... ...
... ... @@ -16,7 +16,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
lint: ^1.8.2
lint: ^1.10.0
flutter:
uses-material-design: true
... ...
... ... @@ -21,6 +21,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
// Image to be sent to the texture
var latestBuffer: CVImageBuffer!
// Return image buffer with the Barcode event
var returnImage: Bool = false
// var analyzeMode: Int = 0
var analyzing: Bool = false
... ... @@ -61,6 +64,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
stop(result)
case "analyzeImage":
analyzeImage(call, result)
default:
result(FlutterMethodNotImplemented)
}
... ... @@ -85,7 +89,17 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
}
return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
}
private func ciImageToJpeg(ciImage: CIImage) -> Data {
// let ciImage = CIImage(cvPixelBuffer: latestBuffer)
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!
let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)
return uiImage.jpegData(compressionQuality: 0.8)!;
}
// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
... ... @@ -108,7 +122,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
scanner.process(image) { [self] barcodes, error in
if error == nil && barcodes != nil {
for barcode in barcodes! {
let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
var event: [String: Any?] = ["name": "barcode", "data": barcode.data]
if (returnImage && latestBuffer != nil) {
let image: CIImage = CIImage(cvPixelBuffer: latestBuffer)
event["image"] = FlutterStandardTypedData(bytes: ciImageToJpeg(ciImage: image))
}
sink?(event)
}
}
... ... @@ -167,6 +187,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
captureSession = AVCaptureSession()
let argReader = MapArgumentReader(call.arguments as? [String: Any])
returnImage = argReader.bool(key: "returnImage") ?? false
// let ratio: Int = argReader.int(key: "ratio")
let torch: Bool = argReader.bool(key: "torch") ?? false
... ...
... ... @@ -15,7 +15,7 @@ An universal scanner for Flutter based on MLKit.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'GoogleMLKit/BarcodeScanning', '~> 2.6.0'
s.dependency 'GoogleMLKit/BarcodeScanning', '~> 3.2.0'
s.platform = :ios, '10.0'
s.static_framework = true
# Flutter.framework does not contain a i386 slice.
... ...
... ... @@ -200,7 +200,11 @@ class MobileScannerWebPlugin {
final code = jsQR(imgData.data, canvas.width, canvas.height);
if (code != null) {
controller.add({'name': 'barcodeWeb', 'data': code.data});
controller.add({
'name': 'barcodeWeb',
'data': code.data,
'binaryData': code.binaryData,
});
}
}
}
... ...
... ... @@ -32,13 +32,13 @@ class MobileScanner extends StatefulWidget {
/// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
const MobileScanner({
Key? key,
super.key,
required this.onDetect,
this.controller,
this.fit = BoxFit.cover,
this.allowDuplicates = false,
this.onPermissionSet,
}) : super(key: key);
});
@override
State<MobileScanner> createState() => _MobileScannerState();
... ... @@ -53,13 +53,14 @@ class _MobileScannerState extends State<MobileScanner>
super.initState();
WidgetsBinding.instance.addObserver(this);
controller = widget.controller ?? MobileScannerController(onPermissionSet: widget.onPermissionSet);
if (!controller.isStarting) controller.start();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
if (!controller.isStarting) controller.start();
if (!controller.isStarting && controller.autoResume) controller.start();
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
... ... @@ -73,44 +74,40 @@ class _MobileScannerState extends State<MobileScanner>
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, BoxConstraints constraints) {
return ValueListenableBuilder(
valueListenable: controller.args,
builder: (context, value, child) {
value = value as MobileScannerArguments?;
if (value == null) {
return Container(color: Colors.black);
return ValueListenableBuilder(
valueListenable: controller.args,
builder: (context, value, child) {
value = value as MobileScannerArguments?;
if (value == null) {
return const ColoredBox(color: Colors.black);
} else {
controller.barcodes.listen((barcode) {
if (!widget.allowDuplicates) {
if (lastScanned != barcode.rawValue) {
lastScanned = barcode.rawValue;
widget.onDetect(barcode, value! as MobileScannerArguments);
}
} else {
controller.barcodes.listen((barcode) {
if (!widget.allowDuplicates) {
if (lastScanned != barcode.rawValue) {
lastScanned = barcode.rawValue;
widget.onDetect(barcode, value! as MobileScannerArguments);
}
} else {
widget.onDetect(barcode, value! as MobileScannerArguments);
}
});
return ClipRect(
widget.onDetect(barcode, value! as MobileScannerArguments);
}
});
return ClipRect(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: FittedBox(
fit: widget.fit,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: FittedBox(
fit: widget.fit,
child: SizedBox(
width: value.size.width,
height: value.size.height,
child: kIsWeb
? HtmlElementView(viewType: value.webId!)
: Texture(textureId: value.textureId!),
),
),
width: value.size.width,
height: value.size.height,
child: kIsWeb
? HtmlElementView(viewType: value.webId!)
: Texture(textureId: value.textureId!),
),
);
}
},
);
),
),
);
}
},
);
}
... ...
... ... @@ -35,7 +35,8 @@ class MobileScannerController {
EventChannel eventChannel =
const EventChannel('dev.steenbakker.mobile_scanner/scanner/event');
int? _controllerHashcode;
//Must be static to keep the same value on new instances
static int? _controllerHashcode;
StreamSubscription? events;
Function(bool permissionGranted)? onPermissionSet;
... ... @@ -44,6 +45,8 @@ class MobileScannerController {
late final ValueNotifier<CameraFacing> cameraFacingState;
final Ratio? ratio;
final bool? torchEnabled;
// Whether to return the image buffer with the Barcode event
final bool returnImage;
/// If provided, the scanner will only detect those specific formats.
///
... ... @@ -54,6 +57,9 @@ class MobileScannerController {
bool hasTorch = false;
late StreamController<Barcode> barcodesController;
/// Whether to automatically resume the camera when the application is resumed
bool autoResume;
Stream<Barcode> get barcodes => barcodesController.stream;
MobileScannerController({
... ... @@ -62,6 +68,8 @@ class MobileScannerController {
this.torchEnabled,
this.formats,
this.onPermissionSet,
this.autoResume = true,
this.returnImage = false,
}) {
// In case a new instance is created before calling dispose()
if (_controllerHashcode != null) {
... ... @@ -77,8 +85,6 @@ class MobileScannerController {
// onCancel: () => setAnalyzeMode(AnalyzeMode.none.index),
);
start();
// Listen to events from the platform specific code
events = eventChannel
.receiveBroadcastStream()
... ... @@ -88,24 +94,32 @@ class MobileScannerController {
void handleEvent(Map event) {
final name = event['name'];
final data = event['data'];
final binaryData = event['binaryData'];
switch (name) {
case 'torchState':
final state = TorchState.values[data as int];
final state = TorchState.values[data as int? ?? 0];
torchState.value = state;
break;
case 'barcode':
final barcode = Barcode.fromNative(data as Map);
final image = returnImage ? event['image'] as Uint8List : null;
final barcode = Barcode.fromNative(data as Map? ?? {}, image);
barcodesController.add(barcode);
break;
case 'barcodeMac':
barcodesController.add(
Barcode(
rawValue: (data as Map)['payload'] as String,
rawValue: (data as Map)['payload'] as String?,
),
);
break;
case 'barcodeWeb':
barcodesController.add(Barcode(rawValue: data as String));
final bytes = (binaryData as List).cast<int>();
barcodesController.add(
Barcode(
rawValue: data as String?,
rawBytes: Uint8List.fromList(bytes),
),
);
break;
default:
throw UnimplementedError();
... ... @@ -136,11 +150,11 @@ class MobileScannerController {
// Check authorization status
if (!kIsWeb) {
MobileScannerState state = MobileScannerState
.values[await methodChannel.invokeMethod('state') as int];
.values[await methodChannel.invokeMethod('state') as int? ?? 0];
switch (state) {
case MobileScannerState.undetermined:
final bool result =
await methodChannel.invokeMethod('request') as bool;
await methodChannel.invokeMethod('request') as bool? ?? false;
state = result
? MobileScannerState.authorized
: MobileScannerState.denied;
... ... @@ -171,6 +185,7 @@ class MobileScannerController {
arguments['formats'] = formats!.map((e) => e.rawValue).toList();
}
}
arguments['returnImage'] = returnImage;
// Start the camera with arguments
Map<String, dynamic>? startResult = {};
... ... @@ -194,7 +209,7 @@ class MobileScannerController {
throw PlatformException(code: 'INITIALIZATION ERROR');
}
hasTorch = startResult['torchable'] as bool;
hasTorch = startResult['torchable'] as bool? ?? false;
if (kIsWeb) {
onPermissionSet?.call(true); // If we reach this line, it means camera permission has been granted
... ... @@ -202,15 +217,15 @@ class MobileScannerController {
args.value = MobileScannerArguments(
webId: startResult['ViewID'] as String?,
size: Size(
startResult['videoWidth'] as double,
startResult['videoHeight'] as double,
startResult['videoWidth'] as double? ?? 0,
startResult['videoHeight'] as double? ?? 0,
),
hasTorch: hasTorch,
);
} else {
args.value = MobileScannerArguments(
textureId: startResult['textureId'] as int,
size: toSize(startResult['size'] as Map),
textureId: startResult['textureId'] as int?,
size: toSize(startResult['size'] as Map? ?? {}),
hasTorch: hasTorch,
);
}
... ...
... ... @@ -12,6 +12,11 @@ class Barcode {
/// Returns null if the corner points can not be determined.
final List<Offset>? corners;
/// Returns raw bytes of the image buffer
///
/// Returns null if the image was not returned
final Uint8List? image;
/// Returns barcode format
final BarcodeFormat format;
... ... @@ -74,6 +79,7 @@ class Barcode {
Barcode({
this.corners,
this.image,
this.format = BarcodeFormat.ean13,
this.rawBytes,
this.type = BarcodeType.text,
... ... @@ -91,7 +97,7 @@ class Barcode {
});
/// Create a [Barcode] from native data.
Barcode.fromNative(Map data)
Barcode.fromNative(Map data, this.image)
: corners = toCorners(data['corners'] as List?),
format = toFormat(data['format'] as int),
rawBytes = data['rawBytes'] as Uint8List?,
... ...
@JS()
library jsqr;
import 'dart:typed_data';
import 'package:js/js.dart';
@JS('jsQR')
... ... @@ -9,4 +11,6 @@ external Code? jsQR(dynamic data, int? width, int? height);
@JS()
class Code {
external String get data;
external Uint8ClampedList get binaryData;
}
... ...
... ... @@ -17,7 +17,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
lint: ^1.8.2
lint: ^1.10.0
flutter:
plugin:
... ...