diff --git a/.metadata b/.metadata new file mode 100644 index 00000000..96128f4f --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 27321ebbad34b0a3fafe99fac037102196d655ff + channel: stable + +project_type: plugin diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c2f164..b52c669b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## Next release + +- iOS code migrated to Swift +- Android code migrated to FlutterPlugin +- Platform communication with protobuf +- Retrieve the number of available cameras with `BarcodeScanner.numberOfCameras` + +Flexible configuration: +- Set the strings for the flash on/off and the cancel button +- Restrict the detected barcode formats +- Set which camera is used for scanning barcodes + +**BREAKING CHANGES**: +- minSdk version on Android is now 18 +- `BarcodeScanner.scan()` returns a `ScanResult` object. Check [UPGRADE.md](./UPGRADE.md) for migration details. + ## v2.0.1 - 2020-02-19 Bugfixes: diff --git a/README.md b/README.md index b43730fe..efd367a5 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,79 @@ To use on iOS, you must add the the camera usage description to your Info.plist ``` +## Usage + +```dart + +import 'package:barcode_scan/barcode_scan.dart'; + +void main() async { + var result = await BarcodeScanner.scan(); + + print(result.type); // The result type (barcode, cancelled, failed) + print(result.rawContent); // The barcode content + print(result.format); // The barcode format (as enum) + print(result.formatNote); // If a unknown format was scanned this field contains a note +} +``` + + +## Advanced usage +You can pass options to the scan method: + +```dart + +import 'package:barcode_scan/barcode_scan.dart'; + +void main() async { + + var options = ScanOptions( + // set the options + ); + + var result = await BarcodeScanner.scan(options: options); + + // ... +} +``` + +### Supported options +| Option | Type | Description | Supported by | +|----------------------------|-------------------|-------------------------------------------------------------------------------------------|---------------| +| `strings.cancel` | `String` | The cancel button text on iOS | iOS only | +| `strings.flash_on` | `String` | The flash on button text | iOS + Android | +| `strings.flash_off` | `String` | The flash off button text | iOS + Android | +| `restrictFormat` | `BarcodeFormat[]` | Restrict the formats which are recognized | iOS + Android | +| `useCamera` | `int` | The index of the camera which is used for scanning (See `BarcodeScanner.numberOfCameras`) | iOS + Android | +| `autoEnableFlash` | `bool` | Enable the flash when start scanning | iOS + Android | +| `android.aspectTolerance` | `double` | Enable the flash when start scanning | Android only | +| `android.useAutoFocus` | `bool` | Enable the flash when start scanning | Android only | + +## Development setup + +### Setup protobuf + +Mac: +```bash +$ brew install protobuf +$ brew install swift-protobuf +``` +Windows / Linux: https://github.com/protocolbuffers/protobuf#protocol-compiler-installation + + +Activate the protobuf dart plugin: +`$ pub global activate protoc_plugin` + +Install the`Protobuf Support` plugin for IDEA / Android Studio or `vscode-proto3` for VS Code + +If you changed the protos.proto you've to execute the ./generate_proto.sh to update the dart / swift sources + + + + + + ## Common problems ### Android "Could not find org.jetbrains.kotlin:kotlin-stdlib-jre..." Change `org.jetbrains.kotlin:kotlin-stdlib-jre` to `org.jetbrains.kotlin:kotlin-stdlib-jdk` -([StackOverflow](https://stackoverflow.com/a/53358817)) +([StackOverflow](https://stackoverflow.com/a/53358817)) \ No newline at end of file diff --git a/UPGRADE.md b/UPGRADE.md index acd1546c..187855d8 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,10 @@ +# Upgrade from 2.x to 3.0.0 + +The `BarcodeScan.scan()` method returns a [`ScanResult`](./lib/model/scan_result.dart). +The barcode is stored in `ScanResult.rawContent` +Check your calls to this method and read the barcode from the `rawContent` property. + + # Upgrade from 1.0.0 to 2.0.0 The simples way for upgrading is by replacing: diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..b453254a --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,11 @@ +include: package:effective_dart/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +analyzer: + exclude: + - lib/gen/** diff --git a/android/build.gradle b/android/build.gradle index 0c36855d..a7e7c75b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,38 +2,40 @@ group 'de.mintware.barcode_scan' version '1.0-SNAPSHOT' buildscript { + ext.kotlin_version = '1.3.61' + ext.protobuf_version = '0.8.8' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61" + classpath 'com.android.tools.build:gradle:3.5.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.google.protobuf:protobuf-gradle-plugin:$protobuf_version" } } rootProject.allprojects { repositories { - jcenter() google() + jcenter() } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'com.google.protobuf' android { compileSdkVersion 29 sourceSets { main.java.srcDirs += 'src/main/kotlin' + main.proto.srcDirs += '../protos' } defaultConfig { - minSdkVersion 16 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" + minSdkVersion 18 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -41,9 +43,32 @@ android { } } + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.11.4' + } + plugins { + javalite { + // The codegen for lite comes as a separate artifact + artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' + } + } + generateProtoTasks { + all().each { task -> + task.plugins { + javalite { } + } + } + } +} + dependencies { - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'me.dm7.barcodescanner:zxing:1.9.13' - api 'com.google.android.material:material:1.0.0' + implementation 'com.google.protobuf:protobuf-lite:3.0.1' + api 'com.google.android.material:material:1.1.0' } diff --git a/android/gradle.properties b/android/gradle.properties index 53ae0ae4..38c8d454 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ -android.enableJetifier=true -android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 13372aef..00000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7225ec2d..d757f3d3 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,3 @@ -#Tue Aug 27 10:46:28 ICT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index 9d82f789..00000000 --- a/android/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index aec99730..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index f84e395f..a095cbdb 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="de.mintware.barcode_scan"> diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/ActivityHelper.kt b/android/src/main/kotlin/de/mintware/barcode_scan/ActivityHelper.kt new file mode 100644 index 00000000..4caba964 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/barcode_scan/ActivityHelper.kt @@ -0,0 +1,41 @@ +package de.mintware.barcode_scan + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.util.Log +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry + +class ActivityHelper(private var applicationContext: Context?, + var activity: Activity? = null +) : PluginRegistry.ActivityResultListener { + + companion object { + const val TAG = "BarcodeScanPlugin" + const val REQ_START_SCAN = 100 + } + + private val resultMap: HashMap = linkedMapOf() + + fun showScannerActivity(result: MethodChannel.Result, config: Protos.Configuration) { + if (activity == null) { + Log.d(TAG, "Could not launch BarcodeScannerActivity because the plugin is not attached to any activity") + return + } + + resultMap[REQ_START_SCAN] = ScanResultHandler(result) + + val intent = Intent(applicationContext, BarcodeScannerActivity::class.java) + intent.putExtra(BarcodeScannerActivity.EXTRA_CONFIG, config.toByteArray()) + activity!!.startActivityForResult(intent, REQ_START_SCAN) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (!resultMap.containsKey(requestCode)) { + return false + } + + return resultMap.getValue(requestCode).handle(requestCode, resultCode, data) + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/ActivityResultHandler.kt b/android/src/main/kotlin/de/mintware/barcode_scan/ActivityResultHandler.kt new file mode 100644 index 00000000..7e20ecc8 --- /dev/null +++ b/android/src/main/kotlin/de/mintware/barcode_scan/ActivityResultHandler.kt @@ -0,0 +1,15 @@ +package de.mintware.barcode_scan + +import android.content.Intent + +/** + * Represents a handler for activity results + */ +interface ActivityResultHandler { + + /** + * Is called on a activity result + * @return True if the result was handled, otherwise false. + */ + fun handle(requestCode: Int, resultCode: Int, data: Intent?): Boolean +} diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScanPlugin.kt b/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScanPlugin.kt index 59539da3..f159ca21 100644 --- a/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScanPlugin.kt +++ b/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScanPlugin.kt @@ -1,57 +1,79 @@ package de.mintware.barcode_scan -import android.app.Activity -import android.content.Intent -import android.util.Log -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry +import androidx.annotation.NonNull +import androidx.annotation.Nullable +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.PluginRegistry.Registrar -class BarcodeScanPlugin(private val registrar: Registrar) : MethodCallHandler, PluginRegistry.ActivityResultListener { - var result: Result? = null +/** BarcodeScanPlugin */ +class BarcodeScanPlugin : FlutterPlugin, ActivityAware { + @Nullable + private var methodCallHandler: MethodCallHandlerImpl? = null + @Nullable + private var activityHelper: ActivityHelper? = null + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + activityHelper = ActivityHelper(flutterPluginBinding.applicationContext) + + methodCallHandler = MethodCallHandlerImpl(activityHelper!!) + methodCallHandler!!.startListening(flutterPluginBinding.binaryMessenger) + } + + // This static function is optional and equivalent to onAttachedToEngine. It supports the old + // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting + // plugin registration via this function while apps migrate to use the new Android APIs + // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. + // + // It is encouraged to share logic between onAttachedToEngine and registerWith to keep + // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called + // depending on the user's project. onAttachedToEngine or registerWith must both be defined + // in the same class. companion object { + @Suppress("unused") @JvmStatic fun registerWith(registrar: Registrar) { - val channel = MethodChannel(registrar.messenger(), "de.mintware.barcode_scan") - val plugin = BarcodeScanPlugin(registrar) - channel.setMethodCallHandler(plugin) - registrar.addActivityResultListener(plugin) + val handler = MethodCallHandlerImpl(ActivityHelper( + registrar.context(), + registrar.activity() + )) + handler.startListening(registrar.messenger()) } } - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "scan") { - this.result = result - showBarcodeView() - } else { - result.notImplemented() + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + if (methodCallHandler == null) { + return } + + methodCallHandler!!.stopListening() + methodCallHandler = null + activityHelper = null } - private fun showBarcodeView() { - if (registrar.activity() == null) { - Log.e("BarcodeScanPlugin", "plugin can't launch scan activity, because plugin is not attached to any activity.") + override fun onDetachedFromActivity() { + if (methodCallHandler == null) { return } - val intent = Intent(registrar.activity(), BarcodeScannerActivity::class.java) - registrar.activity().startActivityForResult(intent, 100) + + activityHelper!!.activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) } - override fun onActivityResult(code: Int, resultCode: Int, data: Intent?): Boolean { - if (code == 100) { - if (resultCode == Activity.RESULT_OK) { - val barcode = data?.getStringExtra("SCAN_RESULT") - barcode?.let { this.result?.success(barcode) } - } else { - val errorCode = data?.getStringExtra("ERROR_CODE") - this.result?.error(errorCode, null, null) - } - return true + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + if (methodCallHandler == null) { + return } - return false + binding.addActivityResultListener(activityHelper!!) + activityHelper!!.activity = binding.activity + } + + override fun onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity() } } diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScannerActivity.kt b/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScannerActivity.kt index 3dcb1a83..651639d2 100644 --- a/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScannerActivity.kt +++ b/android/src/main/kotlin/de/mintware/barcode_scan/BarcodeScannerActivity.kt @@ -5,113 +5,184 @@ import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import android.view.Menu import android.view.MenuItem +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.google.zxing.BarcodeFormat import com.google.zxing.Result import me.dm7.barcodescanner.zxing.ZXingScannerView - class BarcodeScannerActivity : Activity(), ZXingScannerView.ResultHandler { - lateinit var scannerView: me.dm7.barcodescanner.zxing.ZXingScannerView + init { + title = "" + } - companion object { - val REQUEST_TAKE_PHOTO_CAMERA_PERMISSION = 100 - val TOGGLE_FLASH = 200 + private lateinit var config: Protos.Configuration + private var scannerView: ZXingAutofocusScannerView? = null + private var scannerViewInitialized: Boolean = false + companion object { + const val REQUEST_TAKE_PHOTO_CAMERA_PERMISSION = 100 + const val TOGGLE_FLASH = 200 + const val EXTRA_CONFIG = "config" + const val EXTRA_RESULT = "scan_result" + const val EXTRA_ERROR_CODE = "error_code" + + private val formatMap: Map = mapOf( + Protos.BarcodeFormat.aztec to BarcodeFormat.AZTEC, + Protos.BarcodeFormat.code39 to BarcodeFormat.CODE_39, + Protos.BarcodeFormat.code93 to BarcodeFormat.CODE_93, + Protos.BarcodeFormat.code128 to BarcodeFormat.CODE_128, + Protos.BarcodeFormat.dataMatrix to BarcodeFormat.DATA_MATRIX, + Protos.BarcodeFormat.ean8 to BarcodeFormat.EAN_8, + Protos.BarcodeFormat.ean13 to BarcodeFormat.EAN_13, + Protos.BarcodeFormat.interleaved2of5 to BarcodeFormat.ITF, + Protos.BarcodeFormat.pdf417 to BarcodeFormat.PDF_417, + Protos.BarcodeFormat.qr to BarcodeFormat.QR_CODE, + Protos.BarcodeFormat.upce to BarcodeFormat.UPC_E + ) } + // region Activity lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title = "" - scannerView = ZXingScannerView(this) - scannerView.setAutoFocus(true) - // this paramter will make your HUAWEI phone works great! - scannerView.setAspectTolerance(0.5f) + + config = Protos.Configuration.parseFrom(intent.extras!!.getByteArray(EXTRA_CONFIG)) + } + + private fun setupScannerView() { + if (scannerViewInitialized) { + return + } + + scannerView = ZXingAutofocusScannerView(this).apply { + setAutoFocus(config.android.useAutoFocus) + val restrictedFormats = mapRestrictedBarcodeTypes() + if (restrictedFormats.isNotEmpty()) { + setFormats(restrictedFormats) + } + + // this parameter will make your HUAWEI phone works great! + setAspectTolerance(config.android.aspectTolerance.toFloat()) + if (config.autoEnableFlash) { + flash = config.autoEnableFlash + invalidateOptionsMenu() + } + } + setContentView(scannerView) + scannerViewInitialized = true + } + + override fun onPause() { + scannerView?.stopCamera() + super.onPause() + } + + override fun onResume() { + super.onResume() + + if (!requestCameraAccessIfNecessary()) { + setupScannerView() + scannerView?.setResultHandler(this) + startCamera() + } } + // endregion + // region AppBar menu override fun onCreateOptionsMenu(menu: Menu): Boolean { - if (scannerView.flash) { - val item = menu.add(0, - TOGGLE_FLASH, 0, "Flash Off") - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) - } else { - val item = menu.add(0, - TOGGLE_FLASH, 0, "Flash On") + scannerView?.also { + val buttonText = (if (it.flash) config.stringsMap["flash_off"] else config.stringsMap["flash_on"]) + val item = menu.add(0, TOGGLE_FLASH, 0, buttonText) item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) } + return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == TOGGLE_FLASH) { - scannerView.flash = !scannerView.flash + scannerView?.toggleFlash() this.invalidateOptionsMenu() return true } return super.onOptionsItemSelected(item) } - - override fun onResume() { - super.onResume() - scannerView.setResultHandler(this) - // start camera immediately if permission is already given - if (!requestCameraAccessIfNecessary()) { - scannerView.startCamera() - } - } - - override fun onPause() { - super.onPause() - scannerView.stopCamera() - } + // endregion override fun handleResult(result: Result?) { val intent = Intent() - intent.putExtra("SCAN_RESULT", result.toString()) - setResult(Activity.RESULT_OK, intent) - finish() - } - fun finishWithError(errorCode: String) { - val intent = Intent() - intent.putExtra("ERROR_CODE", errorCode) - setResult(Activity.RESULT_CANCELED, intent) + val builder = Protos.ScanResult.newBuilder() + if (result == null) { + + builder.let { + it.format = Protos.BarcodeFormat.unknown + it.rawContent = "No data was scanned" + it.type = Protos.ResultType.Error + } + } else { + + val format = (formatMap.filterValues { it == result.barcodeFormat }.keys.firstOrNull() + ?: Protos.BarcodeFormat.unknown) + + var formatNote = "" + if (format == Protos.BarcodeFormat.unknown) { + formatNote = result.barcodeFormat.toString() + } + + builder.let { + it.format = format + it.formatNote = formatNote + it.rawContent = result.text + it.type = Protos.ResultType.Barcode + } + } + val res = builder.build() + intent.putExtra(EXTRA_RESULT, res.toByteArray()) + setResult(RESULT_OK, intent) finish() } private fun requestCameraAccessIfNecessary(): Boolean { val array = arrayOf(Manifest.permission.CAMERA) - if (ContextCompat - .checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - - ActivityCompat.requestPermissions(this, array, - REQUEST_TAKE_PHOTO_CAMERA_PERMISSION) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, array, REQUEST_TAKE_PHOTO_CAMERA_PERMISSION) return true } return false } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array,grantResults: IntArray) { - when (requestCode) { - REQUEST_TAKE_PHOTO_CAMERA_PERMISSION -> { - if (PermissionUtil.verifyPermissions(grantResults)) { - scannerView.startCamera() - } else { - finishWithError("PERMISSION_NOT_GRANTED") - } - } - else -> { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } + override fun onRequestPermissionsResult(requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (requestCode != REQUEST_TAKE_PHOTO_CAMERA_PERMISSION) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + return + } + if (verifyPermissions(grantResults)) { + startCamera() + } else { + val intent = Intent() + intent.putExtra(EXTRA_ERROR_CODE, "PERMISSION_NOT_GRANTED") + setResult(RESULT_CANCELED, intent) + finish() } } -} -object PermissionUtil { + private fun startCamera() { + scannerView?.run { + if (config.useCamera > -1) { + startCamera(config.useCamera) + } else { + startCamera() + } + } + } /** * Check that all given permissions have been granted by verifying that each entry in the @@ -119,18 +190,26 @@ object PermissionUtil { * @see Activity.onRequestPermissionsResult */ - fun verifyPermissions(grantResults: IntArray): Boolean { - // At least one result must be checked. - if (grantResults.size < 1) { - return false + private fun verifyPermissions(grantResults: IntArray): Boolean { + // Verify that each required permission has been granted, otherwise return false. + return grantResults.isNotEmpty() && grantResults.all { + it == PackageManager.PERMISSION_GRANTED } + } - // Verify that each required permission has been granted, otherwise return false. - for (result in grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - return false + private fun mapRestrictedBarcodeTypes(): List { + val types: MutableList = mutableListOf() + + this.config.restrictFormatList.filterNotNull().forEach { + if (!formatMap.containsKey(it)) { + print("Unrecognized") + return@forEach } + + types.add(formatMap.getValue(it)) } - return true + + return types } } + diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/MethodCallHandlerImpl.kt b/android/src/main/kotlin/de/mintware/barcode_scan/MethodCallHandlerImpl.kt new file mode 100644 index 00000000..b344c01f --- /dev/null +++ b/android/src/main/kotlin/de/mintware/barcode_scan/MethodCallHandlerImpl.kt @@ -0,0 +1,101 @@ +package de.mintware.barcode_scan + +import android.hardware.Camera +import androidx.annotation.Nullable +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.lang.reflect.Method + +class MethodCallHandlerImpl(private val activityHelper: ActivityHelper +) : MethodChannel.MethodCallHandler { + + @Nullable + private var channel: MethodChannel? = null + + @Suppress("unused") + fun scan(call: MethodCall, result: MethodChannel.Result) { + var config: Protos.Configuration = Protos.Configuration.newBuilder() + .putAllStrings(mapOf( + "cancel" to "Cancel", + "flash_on" to "Flash on", + "flash_off" to "Flash off" + )) + .setAndroid(Protos.AndroidConfiguration + .newBuilder() + .setAspectTolerance(0.5) + .setUseAutoFocus(true)) + .addAllRestrictFormat(mutableListOf()) + .setUseCamera(-1) + .build() + + if (call.arguments is ByteArray) { + config = Protos.Configuration.parseFrom(call.arguments as ByteArray) + } + activityHelper.showScannerActivity(result, config) + } + + @Suppress("unused") + fun numberOfCameras(call: MethodCall, result: MethodChannel.Result) { + result.success(Camera.getNumberOfCameras()) + } + + fun startListening(messenger: BinaryMessenger?) { + if (channel != null) { + stopListening() + } + + channel = MethodChannel(messenger, "de.mintware.barcode_scan").apply { + setMethodCallHandler(this@MethodCallHandlerImpl) + } + } + + fun stopListening() { + if (channel == null) { + return + } + + channel!!.setMethodCallHandler(null) + channel = null + } + + // region MethodCallHandler + + // This map holds all available methods in this class + private val methodMap = HashMap() + + // Handles the method call and calls the correct method in this class + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + if (methodMap.isEmpty()) { + fetchMethods() + } + + val method = methodMap[call.method] + if (null == method) { + result.notImplemented() + return + } + + val args = arrayOfNulls(2) + args[0] = call + args[1] = result + + try { + method.invoke(this, *args) + } catch (e: Exception) { + result.error(call.method, e.message, e) + } + + } + + // Fetches all methods in this class + private fun fetchMethods() { + val c = this::class.java + val m = c.declaredMethods + + for (method in m) { + methodMap[method.name] = method + } + } + // endregion +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/ScanResultHandler.kt b/android/src/main/kotlin/de/mintware/barcode_scan/ScanResultHandler.kt new file mode 100644 index 00000000..67733bfd --- /dev/null +++ b/android/src/main/kotlin/de/mintware/barcode_scan/ScanResultHandler.kt @@ -0,0 +1,37 @@ +package de.mintware.barcode_scan + +import android.app.Activity +import android.content.Intent +import io.flutter.plugin.common.MethodChannel + +class ScanResultHandler(private val result: MethodChannel.Result) : ActivityResultHandler { + + override fun handle(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + + var scanResult = ByteArray(0) + when (resultCode) { + Activity.RESULT_OK -> { + scanResult = data?.getByteArrayExtra(BarcodeScannerActivity.EXTRA_RESULT) + ?: scanResult + } + Activity.RESULT_CANCELED -> { + scanResult = Protos.ScanResult.newBuilder() + .setType(Protos.ResultType.Cancelled) + .build() + .toByteArray() + } + else -> { + val errorCode = data?.getStringExtra(BarcodeScannerActivity.EXTRA_ERROR_CODE) + scanResult = Protos.ScanResult.newBuilder() + .setType(Protos.ResultType.Error) + .setFormat(Protos.BarcodeFormat.unknown) + .setRawContent(errorCode) + .build() + .toByteArray() + } + } + result.success(scanResult) + + return true + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/de/mintware/barcode_scan/ZXingAutofocusScannerView.kt b/android/src/main/kotlin/de/mintware/barcode_scan/ZXingAutofocusScannerView.kt new file mode 100644 index 00000000..01a185bc --- /dev/null +++ b/android/src/main/kotlin/de/mintware/barcode_scan/ZXingAutofocusScannerView.kt @@ -0,0 +1,29 @@ +package de.mintware.barcode_scan + +import android.content.Context +import android.hardware.Camera +import android.util.Log +import me.dm7.barcodescanner.core.CameraWrapper +import me.dm7.barcodescanner.zxing.ZXingScannerView + +class ZXingAutofocusScannerView(context: Context) : ZXingScannerView(context) { + + private var callbackFocus = false + + override fun setupCameraPreview(cameraWrapper: CameraWrapper?) { + cameraWrapper?.mCamera?.parameters?.let { parameters -> + try { + parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE + cameraWrapper.mCamera.parameters = parameters + } catch (ex: Exception) { + Log.e("ZXing", "Failed to set CONTINOUS_PICTURE", ex) + callbackFocus = true + } + } + super.setupCameraPreview(cameraWrapper) + } + + override fun setAutoFocus(state: Boolean) { + super.setAutoFocus(callbackFocus) + } +} \ No newline at end of file diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 00000000..1b5cec02 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 27321ebbad34b0a3fafe99fac037102196d655ff + channel: stable + +project_type: app diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 00000000..5e2133eb --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/example/android/.gitignore b/example/android/.gitignore index 1658458c..5050ebe4 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,9 +1,8 @@ *.iml -.gradle +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat /local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures GeneratedPluginRegistrant.java diff --git a/example/android/android.iml b/example/android/android.iml new file mode 100644 index 00000000..69525d6b --- /dev/null +++ b/example/android/android.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5b6de5fa..0dd9b4de 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,8 +1,8 @@ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { - localPropertiesFile.withInputStream { stream -> - localProperties.load(stream) + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) } } @@ -11,13 +11,22 @@ if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 28 - buildToolsVersion "27.0.3" sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -30,10 +39,10 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "de.mintware.barcode_scan_example" - minSdkVersion 16 + minSdkVersion 18 targetSdkVersion 28 - versionCode 1 - versionName "1.0" + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -52,4 +61,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..3379738a --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index e70b91ac..70dbe35b 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,11 +1,5 @@ - - - - + + diff --git a/example/android/app/src/main/kotlin/com/apptreesoftware/barcodescanexample/MainActivity.kt b/example/android/app/src/main/kotlin/com/apptreesoftware/barcodescanexample/MainActivity.kt deleted file mode 100644 index 82062fa9..00000000 --- a/example/android/app/src/main/kotlin/com/apptreesoftware/barcodescanexample/MainActivity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.mintware.barcode_scan_example - -import android.os.Bundle - -import io.flutter.app.FlutterActivity -import io.flutter.plugins.GeneratedPluginRegistrant - -class MainActivity(): FlutterActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - GeneratedPluginRegistrant.registerWith(this) - } -} diff --git a/example/android/app/src/main/kotlin/de/mintware/barcode_scan_example/MainActivity.kt b/example/android/app/src/main/kotlin/de/mintware/barcode_scan_example/MainActivity.kt new file mode 100644 index 00000000..62afd697 --- /dev/null +++ b/example/android/app/src/main/kotlin/de/mintware/barcode_scan_example/MainActivity.kt @@ -0,0 +1,12 @@ +package de.mintware.barcode_scan_example + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine); + } +} diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..3379738a --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/barcode_scan_example_android.iml b/example/android/barcode_scan_example_android.iml new file mode 100644 index 00000000..1029d721 --- /dev/null +++ b/example/android/barcode_scan_example_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index b8ccfd0e..1eddda3f 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.20' + ext.kotlin_version = '1.3.61' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -15,9 +15,6 @@ allprojects { repositories { google() jcenter() - maven { - url "https://maven.google.com" // Google's Maven repository - } } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 38c8d454..a5965ab8 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true -android.enableJetifier=true +android.enableJetifier=true \ No newline at end of file diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 13372aef..00000000 Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 2819f022..296b146b 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/example/android/gradlew b/example/android/gradlew deleted file mode 100755 index 9d82f789..00000000 --- a/example/android/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat deleted file mode 100644 index aec99730..00000000 --- a/example/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 115da6cb..5a2f14fb 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -5,7 +5,7 @@ def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { - pluginsFile.withInputStream { stream -> plugins.load(stream) } + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> diff --git a/example/ios/.gitignore b/example/ios/.gitignore index b60de6f5..61e19b75 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -2,40 +2,41 @@ .vagrant/ .sconsign.dblite .svn/ +build/ .DS_Store *.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m +*.pyc -*.pbxuser *.mode1v3 *.mode2v3 +*.moved-aside +*.pbxuser *.perspectivev3 - -!default.pbxuser +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. !default.mode1v3 !default.mode2v3 +!default.pbxuser !default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/app.flx -/Flutter/app.zip -/Flutter/App.framework -/Flutter/Flutter.framework -/Flutter/Generated.xcconfig -/ServiceDefinitions.json - -Pods/ \ No newline at end of file diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 6c2de808..6b4c0f78 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier @@ -20,10 +20,6 @@ ???? CFBundleVersion 1.0 - UIRequiredDeviceCapabilities - - arm64 - MinimumOSVersion 8.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 9ec6f194..b30a428b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,33 +1,86 @@ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' -if ENV['FLUTTER_FRAMEWORK_DIR'] == nil - abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do + use_frameworks! + use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; - # Pods for Runner - - # Flutter Pods - pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] - - if File.exists? '../.flutter-plugins' - flutter_root = File.expand_path('..') - File.foreach('../.flutter-plugins') { |line| - plugin = line.split(pattern='=') - if plugin.length == 2 - name = plugin[0].strip() - path = plugin[1].strip() - resolved_path = File.expand_path("#{path}/ios", flutter_root) - pod name, :path => resolved_path - else - puts "Invalid plugin specification: #{line}" - end - } + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') end end +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 617fad51..58bc27d6 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -11,12 +11,10 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CB758D7343A0FB9D27CC91C /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C8072F22B7B32BA6730C5387 /* libPods-Runner.a */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7653189528CDB38D81F071AC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5523A44C42E07F9A51E97C1C /* Pods_Runner.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 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 */; }; @@ -40,9 +38,10 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1CB3FBA29B41FC1460B3FFAC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 2B94998A8FD1F35BDDA9CF38 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 5523A44C42E07F9A51E97C1C /* 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 = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -54,8 +53,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C1496B238CD966896DCC7995 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - C8072F22B7B32BA6730C5387 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B1F721BA52D4BAFE2D980861 /* 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 = ""; }; + E24CC3DB754EBEA554F5A548 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,21 +64,31 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - 5CB758D7343A0FB9D27CC91C /* libPods-Runner.a in Frameworks */, + 7653189528CDB38D81F071AC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 3C7717B73810891D4425A279 /* Frameworks */ = { + 31A30000EF797A796C3BAB0A /* Frameworks */ = { isa = PBXGroup; children = ( - C8072F22B7B32BA6730C5387 /* libPods-Runner.a */, + 5523A44C42E07F9A51E97C1C /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; + 3BA09A18C234B5BAED215BD9 /* Pods */ = { + isa = PBXGroup; + children = ( + E24CC3DB754EBEA554F5A548 /* Pods-Runner.debug.xcconfig */, + B1F721BA52D4BAFE2D980861 /* Pods-Runner.release.xcconfig */, + 2B94998A8FD1F35BDDA9CF38 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -99,8 +108,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - A6820B4C2A2582DD608068A9 /* Pods */, - 3C7717B73810891D4425A279 /* Frameworks */, + 3BA09A18C234B5BAED215BD9 /* Pods */, + 31A30000EF797A796C3BAB0A /* Frameworks */, ); sourceTree = ""; }; @@ -135,15 +144,6 @@ name = "Supporting Files"; sourceTree = ""; }; - A6820B4C2A2582DD608068A9 /* Pods */ = { - isa = PBXGroup; - children = ( - C1496B238CD966896DCC7995 /* Pods-Runner.debug.xcconfig */, - 1CB3FBA29B41FC1460B3FFAC /* Pods-Runner.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -151,14 +151,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 7702C78C831665B143C8448B /* [CP] Check Pods Manifest.lock */, + 994C2DB70967A36973150014 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 9B898C697EA984FE43747B57 /* [CP] Embed Pods Frameworks */, + CC5E0DFCC5D00AADA969C942 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -175,21 +175,20 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0830; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -209,9 +208,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -234,50 +231,51 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; }; - 7702C78C831665B143C8448B /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Run Script"; 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\" build"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 994C2DB70967A36973150014 /* [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; }; - 9B898C697EA984FE43747B57 /* [CP] Embed Pods Frameworks */ = { + CC5E0DFCC5D00AADA969C942 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../../../../../development/flutter/bin/cache/artifacts/engine/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -318,6 +316,85 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.mintware.barcode_scan.plugin.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; @@ -328,14 +405,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -376,14 +461,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -403,6 +496,8 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -412,15 +507,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -430,7 +527,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -438,15 +536,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -455,7 +555,8 @@ PRODUCT_BUNDLE_IDENTIFIER = de.mintware.barcode_scan.plugin.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -467,6 +568,7 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -476,6 +578,7 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c958078..3385c924 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - - - - - BuildSystemType - Original - PreviewsEnabled - - - diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index d5b31c6a..70693e4a 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -5,9 +5,9 @@ import Flutter @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self); - return super.application(application, didFinishLaunchingWithOptions: launchOptions); + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d22f10b2..d36b1fab 100644 --- a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -107,6 +107,12 @@ "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" } ], "info" : { diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 79285d60..b884e078 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSCameraUsageDescription @@ -28,10 +28,6 @@ LaunchScreen UIMainStoryboardFile Main - UIRequiredDeviceCapabilities - - arm64 - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/example/lib/main.dart b/example/lib/main.dart index 34a643d0..3a984bbd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,64 +1,281 @@ import 'dart:async'; +import 'dart:io' show Platform; import 'package:barcode_scan/barcode_scan.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { - runApp(new MyApp()); + runApp(_MyApp()); } -class MyApp extends StatefulWidget { +class _MyApp extends StatefulWidget { @override - _MyAppState createState() => new _MyAppState(); + _MyAppState createState() => _MyAppState(); } -class _MyAppState extends State { - String barcode = ""; +class _MyAppState extends State<_MyApp> { + ScanResult scanResult; + + final _flashOnController = TextEditingController(text: "Flash on"); + final _flashOffController = TextEditingController(text: "Flash off"); + final _cancelController = TextEditingController(text: "Cancel"); + + var _aspectTolerance = 0.00; + var _numberOfCameras = 0; + var _selectedCamera = -1; + var _useAutoFocus = true; + var _autoEnableFlash = false; + + static final _possibleFormats = BarcodeFormat.values.toList() + ..removeWhere((e) => e == BarcodeFormat.unknown); + + List selectedFormats = [..._possibleFormats]; @override + // ignore: type_annotate_public_apis initState() { super.initState(); + + Future.delayed(Duration.zero, () async { + _numberOfCameras = await BarcodeScanner.numberOfCameras; + setState(() {}); + }); } @override Widget build(BuildContext context) { - return new MaterialApp( - home: new Scaffold( - appBar: new AppBar( - title: new Text('Barcode Scanner Example'), + var contentList = [ + if (scanResult != null) + Card( + child: Column( + children: [ + ListTile( + title: Text("Result Type"), + subtitle: Text(scanResult.type?.toString() ?? ""), + ), + ListTile( + title: Text("Raw Content"), + subtitle: Text(scanResult.rawContent ?? ""), + ), + ListTile( + title: Text("Format"), + subtitle: Text(scanResult.format?.toString() ?? ""), + ), + ListTile( + title: Text("Format note"), + subtitle: Text(scanResult.formatNote ?? ""), + ), + ], + ), + ), + ListTile( + title: Text("Camera selection"), + dense: true, + enabled: false, + ), + RadioListTile( + onChanged: (v) => setState(() => _selectedCamera = -1), + value: -1, + title: Text("Default camera"), + groupValue: _selectedCamera, + ), + ]; + + for (var i = 0; i < _numberOfCameras; i++) { + contentList.add(RadioListTile( + onChanged: (v) => setState(() => _selectedCamera = i), + value: i, + title: Text("Camera ${i + 1}"), + groupValue: _selectedCamera, + )); + } + + contentList.addAll([ + ListTile( + title: Text("Button Texts"), + dense: true, + enabled: false, + ), + ListTile( + title: TextField( + decoration: InputDecoration( + hasFloatingPlaceholder: true, + labelText: "Flash On", + ), + controller: _flashOnController, + ), + ), + ListTile( + title: TextField( + decoration: InputDecoration( + hasFloatingPlaceholder: true, + labelText: "Flash Off", + ), + controller: _flashOffController, + ), + ), + ListTile( + title: TextField( + decoration: InputDecoration( + hasFloatingPlaceholder: true, + labelText: "Cancel", ), - body: new Center( - child: new Column( - children: [ - new Container( - child: new MaterialButton( - onPressed: scan, child: new Text("Scan")), - padding: const EdgeInsets.all(8.0), - ), - new Text(barcode), - ], - ), - )), + controller: _cancelController, + ), + ), + ]); + + if (Platform.isAndroid) { + contentList.addAll([ + ListTile( + title: Text("Android specific options"), + dense: true, + enabled: false, + ), + ListTile( + title: + Text("Aspect tolerance (${_aspectTolerance.toStringAsFixed(2)})"), + subtitle: Slider( + min: -1.0, + max: 1.0, + value: _aspectTolerance, + onChanged: (value) { + setState(() { + _aspectTolerance = value; + }); + }, + ), + ), + CheckboxListTile( + title: Text("Use autofocus"), + value: _useAutoFocus, + onChanged: (checked) { + setState(() { + _useAutoFocus = checked; + }); + }, + ) + ]); + } + + contentList.addAll([ + ListTile( + title: Text("Other options"), + dense: true, + enabled: false, + ), + CheckboxListTile( + title: Text("Start with flash"), + value: _autoEnableFlash, + onChanged: (checked) { + setState(() { + _autoEnableFlash = checked; + }); + }, + ) + ]); + + contentList.addAll([ + ListTile( + title: Text("Barcode formats"), + dense: true, + enabled: false, + ), + ListTile( + trailing: Checkbox( + tristate: true, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: selectedFormats.length == _possibleFormats.length + ? true + : selectedFormats.length == 0 ? false : null, + onChanged: (checked) { + setState(() { + selectedFormats = [ + if (checked ?? false) ..._possibleFormats, + ]; + }); + }, + ), + dense: true, + enabled: false, + title: Text("Detect barcode formats"), + subtitle: Text( + 'If all are unselected, all possible platform formats will be used', + ), + ), + ]); + + contentList.addAll(_possibleFormats.map( + (format) => CheckboxListTile( + value: selectedFormats.contains(format), + onChanged: (i) { + setState(() => selectedFormats.contains(format) + ? selectedFormats.remove(format) + : selectedFormats.add(format)); + }, + title: Text(format.toString()), + ), + )); + + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + appBar: AppBar( + title: Text('Barcode Scanner Example'), + actions: [ + IconButton( + icon: Icon(Icons.camera), + tooltip: "Scan", + onPressed: scan, + ) + ], + ), + body: ListView( + scrollDirection: Axis.vertical, + shrinkWrap: true, + children: contentList, + ), + ), ); } Future scan() async { try { - String barcode = await BarcodeScanner.scan(); - setState(() => this.barcode = barcode); + var options = ScanOptions( + strings: { + "cancel": _cancelController.text, + "flash_on": _flashOnController.text, + "flash_off": _flashOffController.text, + }, + restrictFormat: selectedFormats, + useCamera: _selectedCamera, + autoEnableFlash: _autoEnableFlash, + android: AndroidOptions( + aspectTolerance: _aspectTolerance, + useAutoFocus: _useAutoFocus, + ), + ); + + var result = await BarcodeScanner.scan(options: options); + + setState(() => scanResult = result); } on PlatformException catch (e) { - if (e.code == BarcodeScanner.CameraAccessDenied) { + var result = ScanResult( + type: ResultType.Error, + format: BarcodeFormat.unknown, + ); + + if (e.code == BarcodeScanner.cameraAccessDenied) { setState(() { - this.barcode = 'The user did not grant the camera permission!'; + result.rawContent = 'The user did not grant the camera permission!'; }); } else { - setState(() => this.barcode = 'Unknown error: $e'); + result.rawContent = 'Unknown error: $e'; } - } on FormatException{ - setState(() => this.barcode = 'null (User returned using the "back"-button before scanning anything. Result)'); - } catch (e) { - setState(() => this.barcode = 'Unknown error: $e'); + setState(() { + scanResult = result; + }); } } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3b38e3f4..9732f3fc 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,6 +15,7 @@ dev_dependencies: barcode_scan: path: ../ + effective_dart: ^1.2.0 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 00000000..9e120a14 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,10 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +void main() { + // todo +} diff --git a/generate_proto.sh b/generate_proto.sh new file mode 100755 index 00000000..2400822c --- /dev/null +++ b/generate_proto.sh @@ -0,0 +1,37 @@ +#!/bin/sh +PROTO_OUT_FILE="./protos/protos.proto" + +if [ -f "$PROTO_OUT_FILE" ]; then + echo "Deleting old $PROTO_OUT_FILE" + rm "$PROTO_OUT_FILE"; +fi + +cat << HEADER > "$PROTO_OUT_FILE" +// AUTO GENERATED FILE, DO NOT EDIT! +// +// Generated by $0 + +syntax = "proto3"; +option java_package = "de.mintware.barcode_scan"; +HEADER + +echo $FILE_HEADER >> $PROTO_OUT_FILE; + +for f in protos/*.proto; do + if [ "./$f" = $PROTO_OUT_FILE ]; then + continue ; + fi; + ( + echo "// $f"; + ## Remove duplicate headers + cat $f | sed -E 's#^(syntax|package|option|import).+$##' | sed '/^$/d'; + echo '' + ) >> $PROTO_OUT_FILE; +done + + +protoc \ +--dart_out="./lib/gen" \ +--swift_out=./ios/Classes \ +"$PROTO_OUT_FILE" + diff --git a/ios/.gitignore b/ios/.gitignore index 956c87f3..aa479fd3 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -9,6 +9,10 @@ profile DerivedData/ build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ *.pbxuser *.mode1v3 @@ -29,3 +33,5 @@ xcuserdata Icon? .tags* +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Classes/BarcodeScanPlugin.h b/ios/Classes/BarcodeScanPlugin.h index d6b1bab6..9101666f 100644 --- a/ios/Classes/BarcodeScanPlugin.h +++ b/ios/Classes/BarcodeScanPlugin.h @@ -1,14 +1,4 @@ #import -#import "BarcodeScannerViewControllerDelegate.h" -#define UIColorFromRGB(rgbValue) \ -[UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \ - green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \ - blue:((float)((rgbValue & 0x0000FF) >> 0))/255.0 \ - alpha:1.0] - -@interface BarcodeScanPlugin : NSObject - -@property(nonatomic, copy) FlutterResult result; -@property (nonatomic, assign) UIViewController *hostViewController; +@interface BarcodeScanPlugin : NSObject @end diff --git a/ios/Classes/BarcodeScanPlugin.m b/ios/Classes/BarcodeScanPlugin.m index ae557c45..6fbaf92b 100644 --- a/ios/Classes/BarcodeScanPlugin.m +++ b/ios/Classes/BarcodeScanPlugin.m @@ -1,45 +1,15 @@ #import "BarcodeScanPlugin.h" -#import "BarcodeScannerViewController.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "barcode_scan-Swift.h" +#endif @implementation BarcodeScanPlugin + (void)registerWithRegistrar:(NSObject*)registrar { - FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"de.mintware.barcode_scan" - binaryMessenger:registrar.messenger]; - BarcodeScanPlugin *instance = [BarcodeScanPlugin new]; - instance.hostViewController = [UIApplication sharedApplication].delegate.window.rootViewController; - [registrar addMethodCallDelegate:instance channel:channel]; + [SwiftBarcodeScanPlugin registerWithRegistrar:registrar]; } - -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"scan" isEqualToString:call.method]) { - self.result = result; - [self showBarcodeView]; - } else { - result(FlutterMethodNotImplemented); - } -} - -- (void)showBarcodeView { - BarcodeScannerViewController *scannerViewController = [[BarcodeScannerViewController alloc] init]; - UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:scannerViewController]; - if (@available(iOS 13.0, *)) { - [navigationController setModalPresentationStyle:UIModalPresentationFullScreen]; - } - scannerViewController.delegate = self; - [self.hostViewController presentViewController:navigationController animated:NO completion:nil]; -} -- (void)barcodeScannerViewController:(BarcodeScannerViewController *)controller didScanBarcodeWithResult:(NSString *)result { - if (self.result) { - self.result(result); - } -} - -- (void)barcodeScannerViewController:(BarcodeScannerViewController *)controller didFailWithErrorCode:(NSString *)errorCode { - if (self.result){ - self.result([FlutterError errorWithCode:errorCode - message:nil - details:nil]); - } -} - @end diff --git a/ios/Classes/BarcodeScannerViewController.h b/ios/Classes/BarcodeScannerViewController.h deleted file mode 100644 index 9d80ace8..00000000 --- a/ios/Classes/BarcodeScannerViewController.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by Matthew Smith on 11/7/17. -// - -#import -#import - -#import "BarcodeScannerViewControllerDelegate.h" -#import "ScannerOverlay.h" - - -@interface BarcodeScannerViewController : UIViewController -@property(nonatomic, retain) UIView *previewView; - @property(nonatomic, retain) ScannerOverlay *scanRect; -@property(nonatomic, retain) MTBBarcodeScanner *scanner; -@property(nonatomic, weak) id delegate; - - - -(id) initWithOptions:(NSDictionary *) options; -@end diff --git a/ios/Classes/BarcodeScannerViewController.m b/ios/Classes/BarcodeScannerViewController.m deleted file mode 100644 index 662c3816..00000000 --- a/ios/Classes/BarcodeScannerViewController.m +++ /dev/null @@ -1,160 +0,0 @@ -// -// Created by Matthew Smith on 11/7/17. -// - -#import "BarcodeScannerViewController.h" -#import -#import "ScannerOverlay.h" - - -@implementation BarcodeScannerViewController { -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - CGRect bounds = [UIScreen mainScreen].bounds; - CGRect reversedBounds = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.height, bounds.size.width); - self.previewView.bounds = reversedBounds; - self.previewView.frame = reversedBounds; - [self.scanRect stopAnimating]; - [self.scanRect removeFromSuperview]; - [self setupScanRect:reversedBounds]; - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; -} - -- (void)setupScanRect:(CGRect)bounds { - self.scanRect = [[ScannerOverlay alloc] initWithFrame:bounds]; - self.scanRect.translatesAutoresizingMaskIntoConstraints = NO; - self.scanRect.backgroundColor = UIColor.clearColor; - [self.view addSubview:_scanRect]; - [self.view addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[scanRect]" - options:NSLayoutFormatAlignAllBottom - metrics:nil - views:@{@"scanRect": _scanRect}]]; - [self.view addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:[scanRect]" - options:NSLayoutFormatAlignAllBottom - metrics:nil - views:@{@"scanRect": _scanRect}]]; - [_scanRect startAnimating]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - self.previewView = [[UIView alloc] initWithFrame:self.view.bounds]; - self.previewView.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:_previewView]; - [self.view addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[previewView]" - options:NSLayoutFormatAlignAllBottom - metrics:nil - views:@{@"previewView": _previewView}]]; - [self.view addConstraints:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:[previewView]" - options:NSLayoutFormatAlignAllBottom - metrics:nil - views:@{@"previewView": _previewView}]]; - [self setupScanRect:self.view.bounds]; - self.scanner = [[MTBBarcodeScanner alloc] initWithPreviewView:_previewView]; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)]; - [self updateFlashButton]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - if (self.scanner.isScanning) { - [self.scanner stopScanning]; - } - [MTBBarcodeScanner requestCameraPermissionWithSuccess:^(BOOL success) { - if (success) { - [self startScan]; - } else { - [self.delegate barcodeScannerViewController:self didFailWithErrorCode:@"PERMISSION_NOT_GRANTED"]; - [self dismissViewControllerAnimated:NO completion:nil]; - } - }]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [self.scanner stopScanning]; - [super viewWillDisappear:animated]; - if ([self isFlashOn]) { - [self toggleFlash:NO]; - } -} - -- (void)startScan { - NSError *error; - [self.scanner startScanningWithResultBlock:^(NSArray *codes) { - [self.scanner stopScanning]; - AVMetadataMachineReadableCodeObject *code = codes.firstObject; - if (code) { - [self.delegate barcodeScannerViewController:self didScanBarcodeWithResult:code.stringValue]; - [self dismissViewControllerAnimated:NO completion:nil]; - } - } error:&error]; -} - -- (void)cancel { - [self.delegate barcodeScannerViewController:self didFailWithErrorCode:@"USER_CANCELED"]; - [self dismissViewControllerAnimated:true completion:nil]; -} - -- (void)updateFlashButton { - if (!self.hasTorch) { - return; - } - if (self.isFlashOn) { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Flash Off" - style:UIBarButtonItemStylePlain - target:self action:@selector(toggle)]; - } else { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Flash On" - style:UIBarButtonItemStylePlain - target:self action:@selector(toggle)]; - } -} - -- (void)toggle { - [self toggleFlash:!self.isFlashOn]; - [self updateFlashButton]; -} - -- (BOOL)isFlashOn { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - if (device) { - return device.torchMode == AVCaptureFlashModeOn || device.torchMode == AVCaptureTorchModeOn; - } - return NO; -} - -- (BOOL)hasTorch { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - if (device) { - return device.hasTorch; - } - return false; -} - -- (void)toggleFlash:(BOOL)on { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - if (!device) return; - - NSError *err; - if (device.hasFlash && device.hasTorch) { - [device lockForConfiguration:&err]; - if (err != nil) return; - if (on) { - device.flashMode = AVCaptureFlashModeOn; - device.torchMode = AVCaptureTorchModeOn; - } else { - device.flashMode = AVCaptureFlashModeOff; - device.torchMode = AVCaptureTorchModeOff; - } - [device unlockForConfiguration]; - } -} - - -@end diff --git a/ios/Classes/BarcodeScannerViewController.swift b/ios/Classes/BarcodeScannerViewController.swift new file mode 100644 index 00000000..858f5eea --- /dev/null +++ b/ios/Classes/BarcodeScannerViewController.swift @@ -0,0 +1,233 @@ +// +// BarcodeScannerViewController.swift +// barcode_scan +// +// Created by Julian Finkler on 20.02.20. +// + +import Foundation +import MTBBarcodeScanner + +class BarcodeScannerViewController: UIViewController { + private var previewView: UIView? + private var scanRect: ScannerOverlay? + private var scanner: MTBBarcodeScanner? + + var config: Configuration = Configuration.with { + $0.strings = [ + "cancel" : "Cancel", + "flash_on" : "Flash on", + "flash_off" : "Flash off", + ] + $0.useCamera = -1 // Default camera + $0.autoEnableFlash = false + } + + private let formatMap = [ + BarcodeFormat.aztec : AVMetadataObject.ObjectType.aztec, + BarcodeFormat.code39 : AVMetadataObject.ObjectType.code39, + BarcodeFormat.code93 : AVMetadataObject.ObjectType.code93, + BarcodeFormat.code128 : AVMetadataObject.ObjectType.code128, + BarcodeFormat.dataMatrix : AVMetadataObject.ObjectType.dataMatrix, + BarcodeFormat.ean8 : AVMetadataObject.ObjectType.ean8, + BarcodeFormat.ean13 : AVMetadataObject.ObjectType.ean13, + BarcodeFormat.interleaved2Of5 : AVMetadataObject.ObjectType.interleaved2of5, + BarcodeFormat.pdf417 : AVMetadataObject.ObjectType.pdf417, + BarcodeFormat.qr : AVMetadataObject.ObjectType.qr, + BarcodeFormat.upce : AVMetadataObject.ObjectType.upce, + ] + + var delegate: BarcodeScannerViewControllerDelegate? + + private var device: AVCaptureDevice? { + return AVCaptureDevice.default(for: .video) + } + + private var isFlashOn: Bool { + return device != nil && (device?.flashMode == AVCaptureDevice.FlashMode.on || device?.torchMode == .on) + } + + private var hasTorch: Bool { + return device?.hasTorch ?? false + } + + override func viewDidLoad() { + super.viewDidLoad() + + #if targetEnvironment(simulator) + view.backgroundColor = .lightGray + #endif + + previewView = UIView(frame: view.bounds) + if let previewView = previewView { + previewView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + view.addSubview(previewView) + } + setupScanRect(view.bounds) + + let restrictedBarcodeTypes = mapRestrictedBarcodeTypes() + if restrictedBarcodeTypes.isEmpty { + scanner = MTBBarcodeScanner(previewView: previewView) + } else { + scanner = MTBBarcodeScanner(metadataObjectTypes: restrictedBarcodeTypes, + previewView: previewView + ) + } + navigationItem.leftBarButtonItem = UIBarButtonItem(title: config.strings["cancel"], + style: .plain, + target: self, + action: #selector(cancel) + ) + updateToggleFlashButton() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if scanner!.isScanning() { + scanner!.stopScanning() + } + + scanRect?.startAnimating() + MTBBarcodeScanner.requestCameraPermission(success: { success in + if success { + self.startScan() + } else { + #if !targetEnvironment(simulator) + self.errorResult(errorCode: "PERMISSION_NOT_GRANTED") + #endif + } + }) + } + + override func viewWillDisappear(_ animated: Bool) { + scanner?.stopScanning() + scanRect?.stopAnimating() + + if isFlashOn { + setFlashState(false) + } + + super.viewWillDisappear(animated) + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + setupScanRect(CGRect(origin: CGPoint(x: 0, y:0), + size: size + )) + } + + private func setupScanRect(_ bounds: CGRect) { + if scanRect != nil { + scanRect?.stopAnimating() + scanRect?.removeFromSuperview() + } + scanRect = ScannerOverlay(frame: bounds) + if let scanRect = scanRect { + scanRect.translatesAutoresizingMaskIntoConstraints = false + scanRect.backgroundColor = UIColor.clear + view.addSubview(scanRect) + scanRect.startAnimating() + } + } + + private func startScan() { + do { + try scanner!.startScanning(with: cameraFromConfig, resultBlock: { codes in + if let code = codes?.first { + let codeType = self.formatMap.first(where: { $0.value == code.type }); + let scanResult = ScanResult.with { + $0.type = .barcode + $0.rawContent = code.stringValue ?? "" + $0.format = codeType?.key ?? .unknown + $0.formatNote = codeType == nil ? code.type.rawValue : "" + } + self.scanner!.stopScanning() + self.scanResult(scanResult) + } + }) + if(config.autoEnableFlash){ + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + self.setFlashState(true) + } + } + } catch { + self.scanResult(ScanResult.with { + $0.type = .error + $0.rawContent = "\(error)" + $0.format = .unknown + }) + } + } + + @objc private func cancel() { + scanResult( ScanResult.with { + $0.type = .cancelled + $0.format = .unknown + }); + } + + @objc private func onToggleFlash() { + setFlashState(!isFlashOn) + } + + private func updateToggleFlashButton() { + if !hasTorch { + return + } + + let buttonText = isFlashOn ? config.strings["flash_off"] : config.strings["flash_on"] + navigationItem.rightBarButtonItem = UIBarButtonItem(title: buttonText, + style: .plain, + target: self, + action: #selector(onToggleFlash) + ) + } + + private func setFlashState(_ on: Bool) { + if let device = device { + guard device.hasFlash && device.hasTorch else { + return + } + + do { + try device.lockForConfiguration() + } catch { + return + } + + device.flashMode = on ? .on : .off + device.torchMode = on ? .on : .off + + device.unlockForConfiguration() + updateToggleFlashButton() + } + } + + private func errorResult(errorCode: String){ + delegate?.didFailWithErrorCode(self, errorCode: errorCode) + dismiss(animated: false) + } + + private func scanResult(_ scanResult: ScanResult){ + self.delegate?.didScanBarcodeWithResult(self, scanResult: scanResult) + dismiss(animated: false) + } + + private func mapRestrictedBarcodeTypes() -> [String] { + var types: [AVMetadataObject.ObjectType] = [] + + config.restrictFormat.forEach({ format in + if let mappedFormat = formatMap[format]{ + types.append(mappedFormat) + } + }) + + return types.map({ t in t.rawValue}) + } + + private var cameraFromConfig: MTBCamera { + return config.useCamera == 1 ? .front : .back + } +} diff --git a/ios/Classes/BarcodeScannerViewControllerDelegate.h b/ios/Classes/BarcodeScannerViewControllerDelegate.h deleted file mode 100644 index 10290a32..00000000 --- a/ios/Classes/BarcodeScannerViewControllerDelegate.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// Created by Matthew Smith on 11/7/17. -// - -#import - -@class BarcodeScannerViewController; - -@protocol BarcodeScannerViewControllerDelegate - -- (void)barcodeScannerViewController:(BarcodeScannerViewController *)controller didScanBarcodeWithResult:(NSString *)result; -- (void)barcodeScannerViewController:(BarcodeScannerViewController *)controller didFailWithErrorCode:(NSString *)errorCode; - -@end diff --git a/ios/Classes/BarcodeScannerViewControllerDelegate.swift b/ios/Classes/BarcodeScannerViewControllerDelegate.swift new file mode 100644 index 00000000..b4b41f50 --- /dev/null +++ b/ios/Classes/BarcodeScannerViewControllerDelegate.swift @@ -0,0 +1,17 @@ +// +// BarcodeScannerViewControllerDelegate.swift +// barcode_scan +// +// Created by Julian Finkler on 20.02.20. +// +import Foundation + +protocol BarcodeScannerViewControllerDelegate { + func didScanBarcodeWithResult(_ controller: BarcodeScannerViewController?, + scanResult: ScanResult + ) + + func didFailWithErrorCode(_ controller: BarcodeScannerViewController?, + errorCode: String + ) +} diff --git a/ios/Classes/ScannerOverlay.h b/ios/Classes/ScannerOverlay.h deleted file mode 100644 index 2067a952..00000000 --- a/ios/Classes/ScannerOverlay.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - -@interface ScannerOverlay : UIView - @property(nonatomic) CGRect scanLineRect; - - - (void) startAnimating; - - (void) stopAnimating; -@end diff --git a/ios/Classes/ScannerOverlay.m b/ios/Classes/ScannerOverlay.m deleted file mode 100644 index 7c415b9c..00000000 --- a/ios/Classes/ScannerOverlay.m +++ /dev/null @@ -1,113 +0,0 @@ -#import "ScannerOverlay.h" - -@interface ScannerOverlay() - @property(nonatomic, retain) UIView *line; -@end - -@implementation ScannerOverlay - - - (instancetype)initWithFrame:(CGRect)frame - { - self = [super initWithFrame:frame]; - if (self) { - _line = [[UIView alloc] init]; - _line.backgroundColor = UIColor.redColor; - _line.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_line]; - } - return self; - } - -- (void)drawRect:(CGRect)rect { - CGContextRef context = UIGraphicsGetCurrentContext(); - - UIColor * overlayColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.55]; - UIColor *scanLineColor = UIColor.redColor; - - CGContextSetFillColorWithColor(context, overlayColor.CGColor); - CGContextFillRect(context, self.bounds); - - // make a hole for the scanner - CGRect holeRect = [self scanRect]; - CGRect holeRectIntersection = CGRectIntersection( holeRect, rect ); - [[UIColor clearColor] setFill]; - UIRectFill(holeRectIntersection); - - // draw a horizontal line over the middle - CGRect lineRect = [self scanLineRect]; - _line.frame = lineRect; - - // drw the green corners - CGFloat cornerSize = 30; - UIBezierPath *path = [UIBezierPath bezierPath]; - //top left corner - [path moveToPoint:CGPointMake(holeRect.origin.x, holeRect.origin.y + cornerSize)]; - [path addLineToPoint:CGPointMake(holeRect.origin.x, holeRect.origin.y)]; - [path addLineToPoint:CGPointMake(holeRect.origin.x + cornerSize, holeRect.origin.y)]; - - //top right corner - CGFloat rightHoleX = holeRect.origin.x + holeRect.size.width; - [path moveToPoint:CGPointMake(rightHoleX - cornerSize, holeRect.origin.y)]; - [path addLineToPoint:CGPointMake(rightHoleX, holeRect.origin.y)]; - [path addLineToPoint:CGPointMake(rightHoleX, holeRect.origin.y + cornerSize)]; - - // bottom right corner - CGFloat bottomHoleY = holeRect.origin.y + holeRect.size.height; - [path moveToPoint:CGPointMake(rightHoleX, bottomHoleY - cornerSize)]; - [path addLineToPoint:CGPointMake(rightHoleX, bottomHoleY)]; - [path addLineToPoint:CGPointMake(rightHoleX - cornerSize, bottomHoleY)]; - - // bottom left corner - [path moveToPoint:CGPointMake(holeRect.origin.x + cornerSize, bottomHoleY)]; - [path addLineToPoint:CGPointMake(holeRect.origin.x, bottomHoleY)]; - [path addLineToPoint:CGPointMake(holeRect.origin.x, bottomHoleY - cornerSize)]; - - path.lineWidth = 2; - [[UIColor greenColor] setStroke]; - [path stroke]; - -} - - - (void)startAnimating { - CABasicAnimation *flash = [CABasicAnimation animationWithKeyPath:@"opacity"]; - flash.fromValue = [NSNumber numberWithFloat:0.0]; - flash.toValue = [NSNumber numberWithFloat:1.0]; - flash.duration = 0.25; - flash.autoreverses = YES; - flash.repeatCount = HUGE_VALF; - [_line.layer addAnimation:flash forKey:@"flashAnimation"]; - } - - - (void)stopAnimating { - [self.layer removeAnimationForKey:@"flashAnimation"]; - } - - - (CGRect)scanRect { - CGRect rect = self.frame; - - CGFloat frameWidth = rect.size.width; - CGFloat frameHeight = rect.size.height; - - BOOL isLandscape = frameWidth > frameHeight; - CGFloat widthOnPortrait = isLandscape ? frameHeight : frameWidth; - CGFloat scanRectWidth = widthOnPortrait * 0.8f; - CGFloat aspectRatio = 3.0/4.0; - CGFloat scanRectHeight = scanRectWidth * aspectRatio; - - if(isLandscape) { - CGFloat navbarHeight = 32; - frameHeight += navbarHeight; - } - - CGFloat scanRectOriginX = (frameWidth - scanRectWidth) / 2; - CGFloat scanRectOriginY = (frameHeight - scanRectHeight) / 2; - return CGRectMake(scanRectOriginX, scanRectOriginY, scanRectWidth, scanRectHeight); - } - - - (CGRect)scanLineRect { - CGRect scanRect = [self scanRect]; - CGFloat positionY = scanRect.origin.y + (scanRect.size.height / 2); - return CGRectMake(scanRect.origin.x, positionY, scanRect.size.width, 1); - } - -@end diff --git a/ios/Classes/ScannerOverlay.swift b/ios/Classes/ScannerOverlay.swift new file mode 100644 index 00000000..16b6b279 --- /dev/null +++ b/ios/Classes/ScannerOverlay.swift @@ -0,0 +1,129 @@ +// +// ScannerOverlay.swift +// barcode_scan +// +// Created by Julian Finkler on 20.02.20. +// + +import Foundation + +class ScannerOverlay: UIView { + private let line: UIView = UIView() + + private var scanLineRect: CGRect { + let scanRect = calculateScanRect() + let positionY = scanRect.origin.y + (scanRect.size.height / 2) + + return CGRect(x: scanRect.origin.x, + y: positionY, + width: scanRect.size.width, + height: 1 + ) + } + + override init(frame: CGRect) { + super.init(frame: frame) + line.backgroundColor = UIColor.red + line.translatesAutoresizingMaskIntoConstraints = false + + addSubview(line) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func draw(_ rect: CGRect) { + let context = UIGraphicsGetCurrentContext() + + let overlayColor = UIColor(red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 0.55 + ) + + context?.setFillColor(overlayColor.cgColor) + context?.fill(bounds) + + // make a hole for the scanner + let holeRect = calculateScanRect() + let holeRectIntersection = holeRect.intersection(rect) + UIColor.clear.setFill() + UIRectFill(holeRectIntersection) + + // draw a horizontal line over the middle + let lineRect = scanLineRect + line.frame = lineRect + + // draw the green corners + let cornerSize: CGFloat = 30 + let path = UIBezierPath() + + //top left corner + path.move(to: CGPoint(x: holeRect.origin.x, y: holeRect.origin.y + cornerSize)) + path.addLine(to: CGPoint(x: holeRect.origin.x, y: holeRect.origin.y)) + path.addLine(to: CGPoint(x: holeRect.origin.x + cornerSize, y: holeRect.origin.y)) + + //top right corner + let rightHoleX = holeRect.origin.x + holeRect.size.width + path.move(to: CGPoint(x: rightHoleX - cornerSize, y: holeRect.origin.y)) + path.addLine(to: CGPoint(x: rightHoleX, y: holeRect.origin.y)) + path.addLine(to: CGPoint(x: rightHoleX, y: holeRect.origin.y + cornerSize)) + + // bottom right corner + let bottomHoleY = holeRect.origin.y + holeRect.size.height + path.move(to: CGPoint(x: rightHoleX, y: bottomHoleY - cornerSize)) + path.addLine(to: CGPoint(x: rightHoleX, y: bottomHoleY)) + path.addLine(to: CGPoint(x: rightHoleX - cornerSize, y: bottomHoleY)) + + // bottom left corner + path.move(to: CGPoint(x: holeRect.origin.x + cornerSize, y: bottomHoleY)) + path.addLine(to: CGPoint(x: holeRect.origin.x, y: bottomHoleY)) + path.addLine(to: CGPoint(x: holeRect.origin.x, y: bottomHoleY - cornerSize)) + + path.lineWidth = 2 + UIColor.green.setStroke() + path.stroke() + } + + public func startAnimating() { + layer.removeAnimation(forKey: "flashAnimation") + let flash = CABasicAnimation(keyPath: "opacity") + flash.fromValue = NSNumber(value: 0.0) + flash.toValue = NSNumber(value: 1.0) + flash.duration = 0.25 + flash.autoreverses = true + flash.repeatCount = HUGE + line.layer.add(flash, forKey: "flashAnimation") + } + + public func stopAnimating() { + layer.removeAnimation(forKey: "flashAnimation") + } + + private func calculateScanRect() -> CGRect { + let rect = frame + + let frameWidth = rect.size.width + var frameHeight = rect.size.height + + let isLandscape = frameWidth > frameHeight + let widthOnPortrait = isLandscape ? frameHeight : frameWidth + let scanRectWidth = widthOnPortrait * 0.8 + let aspectRatio: CGFloat = 3.0 / 4.0 + let scanRectHeight = scanRectWidth * aspectRatio + + if isLandscape { + let navbarHeight: CGFloat = 32 + frameHeight += navbarHeight + } + + let scanRectOriginX = (frameWidth - scanRectWidth) / 2 + let scanRectOriginY = (frameHeight - scanRectHeight) / 2 + return CGRect(x: scanRectOriginX, + y: scanRectOriginY, + width: scanRectWidth, + height: scanRectHeight + ) + } +} diff --git a/ios/Classes/SwiftBarcodeScanPlugin.swift b/ios/Classes/SwiftBarcodeScanPlugin.swift new file mode 100644 index 00000000..463c4e0b --- /dev/null +++ b/ios/Classes/SwiftBarcodeScanPlugin.swift @@ -0,0 +1,71 @@ +import Flutter +import UIKit +import SwiftProtobuf +import AVFoundation + +public class SwiftBarcodeScanPlugin: NSObject, FlutterPlugin, BarcodeScannerViewControllerDelegate { + + private var result: FlutterResult? + private var hostViewController: UIViewController? + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "de.mintware.barcode_scan", binaryMessenger: registrar.messenger()) + let instance = SwiftBarcodeScanPlugin() + instance.hostViewController = UIApplication.shared.delegate?.window??.rootViewController + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + self.result = result + if ("scan" == call.method) { + let configuration: Configuration? = getPayload(call: call) + showBarcodeView(config: configuration)} + else if ("numberOfCameras" == call.method) { + result(AVCaptureDevice.devices(for: .video).count) + } else { + result(FlutterMethodNotImplemented) + } + } + + private func showBarcodeView(config: Configuration? = nil) { + let scannerViewController = BarcodeScannerViewController() + + let navigationController = UINavigationController(rootViewController: scannerViewController) + + if #available(iOS 13.0, *) { + navigationController.modalPresentationStyle = .fullScreen + } + + if let config = config { + scannerViewController.config = config + } + scannerViewController.delegate = self + hostViewController?.present(navigationController, animated: false) + } + + private func getPayload(call: FlutterMethodCall) -> T? { + + if(call.arguments == nil || !(call.arguments is FlutterStandardTypedData)) { + return nil + } + do { + let configuration = try T(serializedData: (call.arguments as! FlutterStandardTypedData).data) + return configuration + } catch { + } + return nil + } + + func didScanBarcodeWithResult(_ controller: BarcodeScannerViewController?, scanResult: ScanResult) { + do { + result?(try scanResult.serializedData()) + } catch { + result?(FlutterError(code: "err_serialize", message: "Failed to serialize the result", details: nil)) + } + } + + func didFailWithErrorCode(_ controller: BarcodeScannerViewController?, errorCode: String) { + result?(FlutterError(code: errorCode, message: nil, details: nil)) + } +} + diff --git a/ios/Classes/protos/protos.pb.swift b/ios/Classes/protos/protos.pb.swift new file mode 100644 index 00000000..c53ec028 --- /dev/null +++ b/ios/Classes/protos/protos.pb.swift @@ -0,0 +1,443 @@ +// DO NOT EDIT. +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: protos/protos.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +// AUTO GENERATED FILE, DO NOT EDIT! +// +// Generated by ./generate_proto.sh + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that your are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// protos/barcode_format.proto +enum BarcodeFormat: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case aztec // = 1 + case code39 // = 2 + case code93 // = 3 + case ean8 // = 4 + case ean13 // = 5 + case code128 // = 6 + case dataMatrix // = 7 + case qr // = 8 + case interleaved2Of5 // = 9 + case upce // = 10 + case pdf417 // = 11 + case UNRECOGNIZED(Int) + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .aztec + case 2: self = .code39 + case 3: self = .code93 + case 4: self = .ean8 + case 5: self = .ean13 + case 6: self = .code128 + case 7: self = .dataMatrix + case 8: self = .qr + case 9: self = .interleaved2Of5 + case 10: self = .upce + case 11: self = .pdf417 + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .aztec: return 1 + case .code39: return 2 + case .code93: return 3 + case .ean8: return 4 + case .ean13: return 5 + case .code128: return 6 + case .dataMatrix: return 7 + case .qr: return 8 + case .interleaved2Of5: return 9 + case .upce: return 10 + case .pdf417: return 11 + case .UNRECOGNIZED(let i): return i + } + } + +} + +#if swift(>=4.2) + +extension BarcodeFormat: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [BarcodeFormat] = [ + .unknown, + .aztec, + .code39, + .code93, + .ean8, + .ean13, + .code128, + .dataMatrix, + .qr, + .interleaved2Of5, + .upce, + .pdf417, + ] +} + +#endif // swift(>=4.2) + +/// protos/scan_result.proto +enum ResultType: SwiftProtobuf.Enum { + typealias RawValue = Int + case barcode // = 0 + case cancelled // = 1 + case error // = 2 + case UNRECOGNIZED(Int) + + init() { + self = .barcode + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .barcode + case 1: self = .cancelled + case 2: self = .error + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .barcode: return 0 + case .cancelled: return 1 + case .error: return 2 + case .UNRECOGNIZED(let i): return i + } + } + +} + +#if swift(>=4.2) + +extension ResultType: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [ResultType] = [ + .barcode, + .cancelled, + .error, + ] +} + +#endif // swift(>=4.2) + +/// protos/android_configuration.proto +struct AndroidConfiguration { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// You can optionally set aspect ratio tolerance level + /// that is used in calculating the optimal Camera preview size. + /// On several Huawei devices you need to set this to 0.5. + /// This parameter is only supported on Android devices. + var aspectTolerance: Double = 0 + + /// Set to true to enable auto focus + /// This parameter is only supported on Android devices. + var useAutoFocus: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// protos/configuration.proto +struct Configuration { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Strings which are displayed to the user + var strings: Dictionary { + get {return _storage._strings} + set {_uniqueStorage()._strings = newValue} + } + + /// Restricts the barcode format which should be read + var restrictFormat: [BarcodeFormat] { + get {return _storage._restrictFormat} + set {_uniqueStorage()._restrictFormat = newValue} + } + + /// Index of the camera which should used. -1 uses the default camera + var useCamera: Int32 { + get {return _storage._useCamera} + set {_uniqueStorage()._useCamera = newValue} + } + + /// Android specific configuration + var android: AndroidConfiguration { + get {return _storage._android ?? AndroidConfiguration()} + set {_uniqueStorage()._android = newValue} + } + /// Returns true if `android` has been explicitly set. + var hasAndroid: Bool {return _storage._android != nil} + /// Clears the value of `android`. Subsequent reads from it will return its default value. + mutating func clearAndroid() {_uniqueStorage()._android = nil} + + /// Set to true to automatically enable flash on camera start + var autoEnableFlash: Bool { + get {return _storage._autoEnableFlash} + set {_uniqueStorage()._autoEnableFlash = newValue} + } + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +struct ScanResult { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Represents the type of the result + var type: ResultType = .barcode + + /// The barcode itself if the result type is barcode. + /// If the result type is error it contains the error message + var rawContent: String = String() + + /// The barcode format + var format: BarcodeFormat = .unknown + + /// If the format is unknown, this field holds additional information + var formatNote: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension BarcodeFormat: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "unknown"), + 1: .same(proto: "aztec"), + 2: .same(proto: "code39"), + 3: .same(proto: "code93"), + 4: .same(proto: "ean8"), + 5: .same(proto: "ean13"), + 6: .same(proto: "code128"), + 7: .same(proto: "dataMatrix"), + 8: .same(proto: "qr"), + 9: .same(proto: "interleaved2of5"), + 10: .same(proto: "upce"), + 11: .same(proto: "pdf417"), + ] +} + +extension ResultType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "Barcode"), + 1: .same(proto: "Cancelled"), + 2: .same(proto: "Error"), + ] +} + +extension AndroidConfiguration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "AndroidConfiguration" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "aspectTolerance"), + 2: .same(proto: "useAutoFocus"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularDoubleField(value: &self.aspectTolerance) + case 2: try decoder.decodeSingularBoolField(value: &self.useAutoFocus) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.aspectTolerance != 0 { + try visitor.visitSingularDoubleField(value: self.aspectTolerance, fieldNumber: 1) + } + if self.useAutoFocus != false { + try visitor.visitSingularBoolField(value: self.useAutoFocus, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: AndroidConfiguration, rhs: AndroidConfiguration) -> Bool { + if lhs.aspectTolerance != rhs.aspectTolerance {return false} + if lhs.useAutoFocus != rhs.useAutoFocus {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Configuration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "Configuration" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "strings"), + 2: .same(proto: "restrictFormat"), + 3: .same(proto: "useCamera"), + 4: .same(proto: "android"), + 5: .same(proto: "autoEnableFlash"), + ] + + fileprivate class _StorageClass { + var _strings: Dictionary = [:] + var _restrictFormat: [BarcodeFormat] = [] + var _useCamera: Int32 = 0 + var _android: AndroidConfiguration? = nil + var _autoEnableFlash: Bool = false + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _strings = source._strings + _restrictFormat = source._restrictFormat + _useCamera = source._useCamera + _android = source._android + _autoEnableFlash = source._autoEnableFlash + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &_storage._strings) + case 2: try decoder.decodeRepeatedEnumField(value: &_storage._restrictFormat) + case 3: try decoder.decodeSingularInt32Field(value: &_storage._useCamera) + case 4: try decoder.decodeSingularMessageField(value: &_storage._android) + case 5: try decoder.decodeSingularBoolField(value: &_storage._autoEnableFlash) + default: break + } + } + } + } + + func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if !_storage._strings.isEmpty { + try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: _storage._strings, fieldNumber: 1) + } + if !_storage._restrictFormat.isEmpty { + try visitor.visitPackedEnumField(value: _storage._restrictFormat, fieldNumber: 2) + } + if _storage._useCamera != 0 { + try visitor.visitSingularInt32Field(value: _storage._useCamera, fieldNumber: 3) + } + if let v = _storage._android { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } + if _storage._autoEnableFlash != false { + try visitor.visitSingularBoolField(value: _storage._autoEnableFlash, fieldNumber: 5) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Configuration, rhs: Configuration) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._strings != rhs_storage._strings {return false} + if _storage._restrictFormat != rhs_storage._restrictFormat {return false} + if _storage._useCamera != rhs_storage._useCamera {return false} + if _storage._android != rhs_storage._android {return false} + if _storage._autoEnableFlash != rhs_storage._autoEnableFlash {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension ScanResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "ScanResult" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "type"), + 2: .same(proto: "rawContent"), + 3: .same(proto: "format"), + 4: .same(proto: "formatNote"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularEnumField(value: &self.type) + case 2: try decoder.decodeSingularStringField(value: &self.rawContent) + case 3: try decoder.decodeSingularEnumField(value: &self.format) + case 4: try decoder.decodeSingularStringField(value: &self.formatNote) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.type != .barcode { + try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1) + } + if !self.rawContent.isEmpty { + try visitor.visitSingularStringField(value: self.rawContent, fieldNumber: 2) + } + if self.format != .unknown { + try visitor.visitSingularEnumField(value: self.format, fieldNumber: 3) + } + if !self.formatNote.isEmpty { + try visitor.visitSingularStringField(value: self.formatNote, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ScanResult, rhs: ScanResult) -> Bool { + if lhs.type != rhs.type {return false} + if lhs.rawContent != rhs.rawContent {return false} + if lhs.format != rhs.format {return false} + if lhs.formatNote != rhs.formatNote {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/ios/barcode_scan.podspec b/ios/barcode_scan.podspec index 831d67ec..ae011f0a 100644 --- a/ios/barcode_scan.podspec +++ b/ios/barcode_scan.podspec @@ -1,5 +1,6 @@ # -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint barcode_scan.podspec' to validate before publishing. # Pod::Spec.new do |s| s.name = 'barcode_scan' @@ -13,9 +14,12 @@ A new flutter plugin project. s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.dependency 'MTBBarcodeScanner' - s.ios.deployment_target = '8.0' -end + s.dependency 'SwiftProtobuf' + s.platform = :ios, '8.0' + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/lib/barcode_scan.dart b/lib/barcode_scan.dart index 80004451..12af6b38 100644 --- a/lib/barcode_scan.dart +++ b/lib/barcode_scan.dart @@ -1,23 +1,6 @@ -import 'dart:async'; +library mintware.barcode_scan; -import 'package:flutter/services.dart'; +export 'gen/protos/protos.pb.dart' show BarcodeFormat, ResultType; -/// Barcode scanner plugin -/// Simply call `var barcode = await BarcodeScanner.scan()` to scan a barcode -class BarcodeScanner { - /// If the user has not granted the access to the camera this code is thrown. - static const CameraAccessDenied = 'PERMISSION_NOT_GRANTED'; - - /// If the user cancel the scan an exception with this code is thrown. - static const UserCanceled = 'USER_CANCELED'; - - /// The method channel - static const MethodChannel _channel = - const MethodChannel('de.mintware.barcode_scan'); - - /// Starts the camera for scanning the barcode, shows a preview window and - /// returns the barcode if one was scanned. - /// Can throw an exception. - /// See also [CameraAccessDenied] and [UserCanceled] - static Future scan() async => await _channel.invokeMethod('scan'); -} +export 'model/model.dart'; +export 'platform_wrapper.dart'; diff --git a/lib/gen/protos/protos.pb.dart b/lib/gen/protos/protos.pb.dart new file mode 100644 index 00000000..ad69181b --- /dev/null +++ b/lib/gen/protos/protos.pb.dart @@ -0,0 +1,178 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'protos.pbenum.dart'; + +export 'protos.pbenum.dart'; + +class AndroidConfiguration extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('AndroidConfiguration', createEmptyInstance: create) + ..a<$core.double>(1, 'aspectTolerance', $pb.PbFieldType.OD, protoName: 'aspectTolerance') + ..aOB(2, 'useAutoFocus', protoName: 'useAutoFocus') + ..hasRequiredFields = false + ; + + AndroidConfiguration._() : super(); + factory AndroidConfiguration() => create(); + factory AndroidConfiguration.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory AndroidConfiguration.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + AndroidConfiguration clone() => AndroidConfiguration()..mergeFromMessage(this); + AndroidConfiguration copyWith(void Function(AndroidConfiguration) updates) => super.copyWith((message) => updates(message as AndroidConfiguration)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static AndroidConfiguration create() => AndroidConfiguration._(); + AndroidConfiguration createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static AndroidConfiguration getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static AndroidConfiguration _defaultInstance; + + @$pb.TagNumber(1) + $core.double get aspectTolerance => $_getN(0); + @$pb.TagNumber(1) + set aspectTolerance($core.double v) { $_setDouble(0, v); } + @$pb.TagNumber(1) + $core.bool hasAspectTolerance() => $_has(0); + @$pb.TagNumber(1) + void clearAspectTolerance() => clearField(1); + + @$pb.TagNumber(2) + $core.bool get useAutoFocus => $_getBF(1); + @$pb.TagNumber(2) + set useAutoFocus($core.bool v) { $_setBool(1, v); } + @$pb.TagNumber(2) + $core.bool hasUseAutoFocus() => $_has(1); + @$pb.TagNumber(2) + void clearUseAutoFocus() => clearField(2); +} + +class Configuration extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('Configuration', createEmptyInstance: create) + ..m<$core.String, $core.String>(1, 'strings', entryClassName: 'Configuration.StringsEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OS) + ..pc(2, 'restrictFormat', $pb.PbFieldType.PE, protoName: 'restrictFormat', valueOf: BarcodeFormat.valueOf, enumValues: BarcodeFormat.values) + ..a<$core.int>(3, 'useCamera', $pb.PbFieldType.O3, protoName: 'useCamera') + ..aOM(4, 'android', subBuilder: AndroidConfiguration.create) + ..aOB(5, 'autoEnableFlash', protoName: 'autoEnableFlash') + ..hasRequiredFields = false + ; + + Configuration._() : super(); + factory Configuration() => create(); + factory Configuration.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Configuration.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + Configuration clone() => Configuration()..mergeFromMessage(this); + Configuration copyWith(void Function(Configuration) updates) => super.copyWith((message) => updates(message as Configuration)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static Configuration create() => Configuration._(); + Configuration createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Configuration getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Configuration _defaultInstance; + + @$pb.TagNumber(1) + $core.Map<$core.String, $core.String> get strings => $_getMap(0); + + @$pb.TagNumber(2) + $core.List get restrictFormat => $_getList(1); + + @$pb.TagNumber(3) + $core.int get useCamera => $_getIZ(2); + @$pb.TagNumber(3) + set useCamera($core.int v) { $_setSignedInt32(2, v); } + @$pb.TagNumber(3) + $core.bool hasUseCamera() => $_has(2); + @$pb.TagNumber(3) + void clearUseCamera() => clearField(3); + + @$pb.TagNumber(4) + AndroidConfiguration get android => $_getN(3); + @$pb.TagNumber(4) + set android(AndroidConfiguration v) { setField(4, v); } + @$pb.TagNumber(4) + $core.bool hasAndroid() => $_has(3); + @$pb.TagNumber(4) + void clearAndroid() => clearField(4); + @$pb.TagNumber(4) + AndroidConfiguration ensureAndroid() => $_ensure(3); + + @$pb.TagNumber(5) + $core.bool get autoEnableFlash => $_getBF(4); + @$pb.TagNumber(5) + set autoEnableFlash($core.bool v) { $_setBool(4, v); } + @$pb.TagNumber(5) + $core.bool hasAutoEnableFlash() => $_has(4); + @$pb.TagNumber(5) + void clearAutoEnableFlash() => clearField(5); +} + +class ScanResult extends $pb.GeneratedMessage { + static final $pb.BuilderInfo _i = $pb.BuilderInfo('ScanResult', createEmptyInstance: create) + ..e(1, 'type', $pb.PbFieldType.OE, defaultOrMaker: ResultType.Barcode, valueOf: ResultType.valueOf, enumValues: ResultType.values) + ..aOS(2, 'rawContent', protoName: 'rawContent') + ..e(3, 'format', $pb.PbFieldType.OE, defaultOrMaker: BarcodeFormat.unknown, valueOf: BarcodeFormat.valueOf, enumValues: BarcodeFormat.values) + ..aOS(4, 'formatNote', protoName: 'formatNote') + ..hasRequiredFields = false + ; + + ScanResult._() : super(); + factory ScanResult() => create(); + factory ScanResult.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ScanResult.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + ScanResult clone() => ScanResult()..mergeFromMessage(this); + ScanResult copyWith(void Function(ScanResult) updates) => super.copyWith((message) => updates(message as ScanResult)); + $pb.BuilderInfo get info_ => _i; + @$core.pragma('dart2js:noInline') + static ScanResult create() => ScanResult._(); + ScanResult createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ScanResult getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ScanResult _defaultInstance; + + @$pb.TagNumber(1) + ResultType get type => $_getN(0); + @$pb.TagNumber(1) + set type(ResultType v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasType() => $_has(0); + @$pb.TagNumber(1) + void clearType() => clearField(1); + + @$pb.TagNumber(2) + $core.String get rawContent => $_getSZ(1); + @$pb.TagNumber(2) + set rawContent($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasRawContent() => $_has(1); + @$pb.TagNumber(2) + void clearRawContent() => clearField(2); + + @$pb.TagNumber(3) + BarcodeFormat get format => $_getN(2); + @$pb.TagNumber(3) + set format(BarcodeFormat v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasFormat() => $_has(2); + @$pb.TagNumber(3) + void clearFormat() => clearField(3); + + @$pb.TagNumber(4) + $core.String get formatNote => $_getSZ(3); + @$pb.TagNumber(4) + set formatNote($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasFormatNote() => $_has(3); + @$pb.TagNumber(4) + void clearFormatNote() => clearField(4); +} + diff --git a/lib/gen/protos/protos.pbenum.dart b/lib/gen/protos/protos.pbenum.dart new file mode 100644 index 00000000..bb4f1456 --- /dev/null +++ b/lib/gen/protos/protos.pbenum.dart @@ -0,0 +1,63 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME +import 'dart:core' as $core; +import 'package:protobuf/protobuf.dart' as $pb; + +class BarcodeFormat extends $pb.ProtobufEnum { + static const BarcodeFormat unknown = BarcodeFormat._(0, 'unknown'); + static const BarcodeFormat aztec = BarcodeFormat._(1, 'aztec'); + static const BarcodeFormat code39 = BarcodeFormat._(2, 'code39'); + static const BarcodeFormat code93 = BarcodeFormat._(3, 'code93'); + static const BarcodeFormat ean8 = BarcodeFormat._(4, 'ean8'); + static const BarcodeFormat ean13 = BarcodeFormat._(5, 'ean13'); + static const BarcodeFormat code128 = BarcodeFormat._(6, 'code128'); + static const BarcodeFormat dataMatrix = BarcodeFormat._(7, 'dataMatrix'); + static const BarcodeFormat qr = BarcodeFormat._(8, 'qr'); + static const BarcodeFormat interleaved2of5 = BarcodeFormat._(9, 'interleaved2of5'); + static const BarcodeFormat upce = BarcodeFormat._(10, 'upce'); + static const BarcodeFormat pdf417 = BarcodeFormat._(11, 'pdf417'); + + static const $core.List values = [ + unknown, + aztec, + code39, + code93, + ean8, + ean13, + code128, + dataMatrix, + qr, + interleaved2of5, + upce, + pdf417, + ]; + + static final $core.Map<$core.int, BarcodeFormat> _byValue = $pb.ProtobufEnum.initByValue(values); + static BarcodeFormat valueOf($core.int value) => _byValue[value]; + + const BarcodeFormat._($core.int v, $core.String n) : super(v, n); +} + +class ResultType extends $pb.ProtobufEnum { + static const ResultType Barcode = ResultType._(0, 'Barcode'); + static const ResultType Cancelled = ResultType._(1, 'Cancelled'); + static const ResultType Error = ResultType._(2, 'Error'); + + static const $core.List values = [ + Barcode, + Cancelled, + Error, + ]; + + static final $core.Map<$core.int, ResultType> _byValue = $pb.ProtobufEnum.initByValue(values); + static ResultType valueOf($core.int value) => _byValue[value]; + + const ResultType._($core.int v, $core.String n) : super(v, n); +} + diff --git a/lib/gen/protos/protos.pbjson.dart b/lib/gen/protos/protos.pbjson.dart new file mode 100644 index 00000000..958b3c14 --- /dev/null +++ b/lib/gen/protos/protos.pbjson.dart @@ -0,0 +1,73 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +const BarcodeFormat$json = const { + '1': 'BarcodeFormat', + '2': const [ + const {'1': 'unknown', '2': 0}, + const {'1': 'aztec', '2': 1}, + const {'1': 'code39', '2': 2}, + const {'1': 'code93', '2': 3}, + const {'1': 'ean8', '2': 4}, + const {'1': 'ean13', '2': 5}, + const {'1': 'code128', '2': 6}, + const {'1': 'dataMatrix', '2': 7}, + const {'1': 'qr', '2': 8}, + const {'1': 'interleaved2of5', '2': 9}, + const {'1': 'upce', '2': 10}, + const {'1': 'pdf417', '2': 11}, + ], +}; + +const ResultType$json = const { + '1': 'ResultType', + '2': const [ + const {'1': 'Barcode', '2': 0}, + const {'1': 'Cancelled', '2': 1}, + const {'1': 'Error', '2': 2}, + ], +}; + +const AndroidConfiguration$json = const { + '1': 'AndroidConfiguration', + '2': const [ + const {'1': 'aspectTolerance', '3': 1, '4': 1, '5': 1, '10': 'aspectTolerance'}, + const {'1': 'useAutoFocus', '3': 2, '4': 1, '5': 8, '10': 'useAutoFocus'}, + ], +}; + +const Configuration$json = const { + '1': 'Configuration', + '2': const [ + const {'1': 'strings', '3': 1, '4': 3, '5': 11, '6': '.Configuration.StringsEntry', '10': 'strings'}, + const {'1': 'restrictFormat', '3': 2, '4': 3, '5': 14, '6': '.BarcodeFormat', '10': 'restrictFormat'}, + const {'1': 'useCamera', '3': 3, '4': 1, '5': 5, '10': 'useCamera'}, + const {'1': 'android', '3': 4, '4': 1, '5': 11, '6': '.AndroidConfiguration', '10': 'android'}, + const {'1': 'autoEnableFlash', '3': 5, '4': 1, '5': 8, '10': 'autoEnableFlash'}, + ], + '3': const [Configuration_StringsEntry$json], +}; + +const Configuration_StringsEntry$json = const { + '1': 'StringsEntry', + '2': const [ + const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + const {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': const {'7': true}, +}; + +const ScanResult$json = const { + '1': 'ScanResult', + '2': const [ + const {'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.ResultType', '10': 'type'}, + const {'1': 'rawContent', '3': 2, '4': 1, '5': 9, '10': 'rawContent'}, + const {'1': 'format', '3': 3, '4': 1, '5': 14, '6': '.BarcodeFormat', '10': 'format'}, + const {'1': 'formatNote', '3': 4, '4': 1, '5': 9, '10': 'formatNote'}, + ], +}; + diff --git a/lib/gen/protos/protos.pbserver.dart b/lib/gen/protos/protos.pbserver.dart new file mode 100644 index 00000000..4aebf0bc --- /dev/null +++ b/lib/gen/protos/protos.pbserver.dart @@ -0,0 +1,9 @@ +/// +// Generated code. Do not modify. +// source: protos/protos.proto +// +// @dart = 2.3 +// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type + +export 'protos.pb.dart'; + diff --git a/lib/model/android_options.dart b/lib/model/android_options.dart new file mode 100644 index 00000000..1733fd9b --- /dev/null +++ b/lib/model/android_options.dart @@ -0,0 +1,18 @@ +/// Android specific scan options +class AndroidOptions { + /// You can optionally set aspect ratio tolerance level + /// that is used in calculating the optimal Camera preview size. + /// This parameter is only supported on Android devices. + final double aspectTolerance; + + /// Set to true to enable auto focus + /// This parameter is only supported on Android devices. + final bool useAutoFocus; + + /// Create Android specific scan options + const AndroidOptions({ + this.aspectTolerance = 0.5, + this.useAutoFocus = true, + }) : assert(aspectTolerance != null), + assert(useAutoFocus != null); +} diff --git a/lib/model/model.dart b/lib/model/model.dart new file mode 100644 index 00000000..6a6da71b --- /dev/null +++ b/lib/model/model.dart @@ -0,0 +1,5 @@ +library mintware.barcode_scan; + +export 'android_options.dart'; +export 'scan_options.dart'; +export 'scan_result.dart'; diff --git a/lib/model/scan_options.dart b/lib/model/scan_options.dart new file mode 100644 index 00000000..dfd3eaa3 --- /dev/null +++ b/lib/model/scan_options.dart @@ -0,0 +1,41 @@ +import '../gen/protos/protos.pb.dart'; +import '../model/android_options.dart'; + +/// Provides options to configure the barcode scanner +class ScanOptions { + /// This map contains strings which are displayed to the user + /// + /// Possible pairs: + /// - cancel : The text of the cancel button (iOS only) + // - flash_on : The text of the flash on button + // - flash_off : The Text of the flash off button + final Map strings; + + /// Restrict the supported barcode formats + final List restrictFormat; + + /// Index of the camera which should used. -1 uses the default camera + final int useCamera; + + /// Android specific configuration + final AndroidOptions android; + + /// Set to true to automatically enable flash on camera start + final bool autoEnableFlash; + + /// Create a object which represents the scanner options + const ScanOptions({ + this.restrictFormat = const [], + this.useCamera = -1, + this.android = const AndroidOptions(), + this.autoEnableFlash = false, + this.strings = const { + "cancel": "Cancel", + "flash_on": "Flash on", + "flash_off": "Flash off", + }, + }) : assert(restrictFormat != null), + assert(useCamera != null), + assert(useCamera >= -1), + assert(android != null); +} diff --git a/lib/model/scan_result.dart b/lib/model/scan_result.dart new file mode 100644 index 00000000..80dac5ad --- /dev/null +++ b/lib/model/scan_result.dart @@ -0,0 +1,28 @@ +import '../gen/protos/protos.pb.dart'; + +/// Represents the result of a scan +class ScanResult { + /// Represents the type of the result + ResultType type; + + /// The barcode itself if the result type is barcode. + /// If the result type is error it contains the error message + String rawContent; + + /// The barcode format + BarcodeFormat format; + + /// If the format is unknown, this field holds additional information + String formatNote; + + /// Creates a new scan result + ScanResult({ + this.type = ResultType.Barcode, + this.rawContent = "", + this.format = BarcodeFormat.unknown, + this.formatNote = "", + }) : assert(type != null), + assert(rawContent != null), + assert(format != null), + assert(formatNote != null); +} diff --git a/lib/platform_wrapper.dart b/lib/platform_wrapper.dart new file mode 100644 index 00000000..3b7074d8 --- /dev/null +++ b/lib/platform_wrapper.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +import './model/model.dart'; +import 'gen/protos/protos.pb.dart' as proto; + +// ignore: avoid_classes_with_only_static_members +/// Barcode scanner plugin +/// Simply call `var barcode = await BarcodeScanner.scan()` to scan a barcode +class BarcodeScanner { + /// If the user has not granted the access to the camera this code is thrown. + static const cameraAccessDenied = 'PERMISSION_NOT_GRANTED'; + + /// The method channel + static const MethodChannel _channel = + MethodChannel('de.mintware.barcode_scan'); + + /// Starts the camera for scanning the barcode, shows a preview window and + /// returns the barcode if one was scanned. + /// Can throw an exception. + /// See also [cameraAccessDenied] + static Future scan({ + ScanOptions options = const ScanOptions(), + }) async { + assert(options != null); + var config = proto.Configuration() + ..useCamera = options.useCamera + ..restrictFormat.addAll(options.restrictFormat) + ..autoEnableFlash = options.autoEnableFlash + ..strings.addAll(options.strings) + ..android = (proto.AndroidConfiguration() + ..useAutoFocus = options.android.useAutoFocus + ..aspectTolerance = options.android.aspectTolerance + /**/) + /**/; + var buffer = await _channel.invokeMethod('scan', config?.writeToBuffer()); + var tmpResult = proto.ScanResult.fromBuffer(buffer); + return ScanResult( + format: tmpResult.format, + formatNote: tmpResult.formatNote, + rawContent: tmpResult.rawContent, + type: tmpResult.type, + ); + } + + /// Returns the number of cameras which are available + /// Use n-1 as the index of the camera which should be used. + static Future get numberOfCameras { + return _channel.invokeMethod('numberOfCameras'); + } +} diff --git a/protos/android_configuration.proto b/protos/android_configuration.proto new file mode 100644 index 00000000..286e5aeb --- /dev/null +++ b/protos/android_configuration.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package de.mintware.barcode_scan; +option java_package = "de.mintware.barcode_scan"; + +message AndroidConfiguration { + // You can optionally set aspect ratio tolerance level + // that is used in calculating the optimal Camera preview size. + // On several Huawei devices you need to set this to 0.5. + // This parameter is only supported on Android devices. + double aspectTolerance = 1; + + // Set to true to enable auto focus + // This parameter is only supported on Android devices. + bool useAutoFocus = 2; +} \ No newline at end of file diff --git a/protos/barcode_format.proto b/protos/barcode_format.proto new file mode 100644 index 00000000..a72de2b5 --- /dev/null +++ b/protos/barcode_format.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package de.mintware.barcode_scan; +option java_package = "de.mintware.barcode_scan"; + +enum BarcodeFormat { + unknown = 0; + aztec = 1; + code39 = 2; + code93 = 3; + ean8 = 4; + ean13 = 5; + code128 = 6; + dataMatrix = 7; + qr = 8; + interleaved2of5 = 9; + upce = 10; + pdf417 = 11; +} \ No newline at end of file diff --git a/protos/configuration.proto b/protos/configuration.proto new file mode 100644 index 00000000..260b42c3 --- /dev/null +++ b/protos/configuration.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package de.mintware.barcode_scan; +import "android_configuration.proto"; +import "barcode_format.proto"; + +message Configuration { + // Strings which are displayed to the user + map strings = 1; + + // Restricts the barcode format which should be read + repeated BarcodeFormat restrictFormat = 2; + + // Index of the camera which should used. -1 uses the default camera + int32 useCamera = 3; + + // Android specific configuration + AndroidConfiguration android = 4; + + // Set to true to automatically enable flash on camera start + bool autoEnableFlash = 5; +} diff --git a/protos/protos.proto b/protos/protos.proto new file mode 100644 index 00000000..cf508b67 --- /dev/null +++ b/protos/protos.proto @@ -0,0 +1,67 @@ +// AUTO GENERATED FILE, DO NOT EDIT! +// +// Generated by ./generate_proto.sh + +syntax = "proto3"; +option java_package = "de.mintware.barcode_scan"; + +// protos/android_configuration.proto +message AndroidConfiguration { + // You can optionally set aspect ratio tolerance level + // that is used in calculating the optimal Camera preview size. + // On several Huawei devices you need to set this to 0.5. + // This parameter is only supported on Android devices. + double aspectTolerance = 1; + // Set to true to enable auto focus + // This parameter is only supported on Android devices. + bool useAutoFocus = 2; +} + +// protos/barcode_format.proto +enum BarcodeFormat { + unknown = 0; + aztec = 1; + code39 = 2; + code93 = 3; + ean8 = 4; + ean13 = 5; + code128 = 6; + dataMatrix = 7; + qr = 8; + interleaved2of5 = 9; + upce = 10; + pdf417 = 11; +} + +// protos/configuration.proto +message Configuration { + // Strings which are displayed to the user + map strings = 1; + // Restricts the barcode format which should be read + repeated BarcodeFormat restrictFormat = 2; + // Index of the camera which should used. -1 uses the default camera + int32 useCamera = 3; + // Android specific configuration + AndroidConfiguration android = 4; + // Set to true to automatically enable flash on camera start + bool autoEnableFlash = 5; +} + +// protos/scan_result.proto +enum ResultType { + Barcode = 0; + Cancelled = 1; + Error = 2; +} +message ScanResult { + // Represents the type of the result + ResultType type = 1; + // The barcode itself if the result type is barcode. + // If the result type is error it contains the error message + string rawContent = 2; + // The barcode format + BarcodeFormat format = 3; + // If the format is unknown, this field holds additional information + string formatNote = 4; +} + diff --git a/protos/scan_result.proto b/protos/scan_result.proto new file mode 100644 index 00000000..4b13dbc1 --- /dev/null +++ b/protos/scan_result.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package de.mintware.barcode_scan; +option java_package = "de.mintware.barcode_scan"; + +import "barcode_format.proto"; + +enum ResultType { + Barcode = 0; + Cancelled = 1; + Error = 2; +} + +message ScanResult { + // Represents the type of the result + ResultType type = 1; + + // The barcode itself if the result type is barcode. + // If the result type is error it contains the error message + string rawContent = 2; + + // The barcode format + BarcodeFormat format = 3; + + // If the format is unknown, this field holds additional information + string formatNote = 4; +} diff --git a/pubspec.yaml b/pubspec.yaml index a897da35..90bc8cea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,16 @@ name: barcode_scan description: A flutter plugin for scanning 2D barcodes and QRCodes via camera. -version: 2.0.1 +version: 3.0.0-dev.1 homepage: https://github.com/mintware-de/flutter_barcode_reader dependencies: + protobuf: ^1.0.1 flutter: sdk: flutter +dev_dependencies: + effective_dart: ^1.2.0 + environment: sdk: ">=2.0.0-dev.58.0 <3.0.0" diff --git a/test/barcode_scan_test.dart b/test/barcode_scan_test.dart new file mode 100644 index 00000000..744b3012 --- /dev/null +++ b/test/barcode_scan_test.dart @@ -0,0 +1,3 @@ +void main() { + // todo +}