new plugin replacing mobile_scanner

This commit is contained in:
Adam Velebil 2022-04-22 11:22:59 +02:00
parent e8d45c2208
commit cf212de322
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
52 changed files with 1393 additions and 32 deletions

View File

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -0,0 +1,30 @@
# 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.
version:
revision: 13a2fb10b838971ce211230f8ffdd094c14af02c
channel: beta
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c
base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c
- platform: android
create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c
base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -0,0 +1,3 @@
## 1.0.0
Initial

View File

@ -0,0 +1,4 @@
# qscanner_mlkit
Android-only Flutter plugin for scanning QR Codes. Implemented with ML Kit and CameraX

View File

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@ -0,0 +1,61 @@
group 'com.yubico.authenticator.flutter_plugins.qrscanner_mlkit'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.6.21'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 31
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 21
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def camerax_version = "1.1.0-beta03"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
}

View File

@ -0,0 +1,15 @@
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Fri Apr 22 08:06:54 CEST 2022
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip

View File

@ -0,0 +1 @@
rootProject.name = 'qrscanner_mlkit'

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yubico.authenticator.flutter_plugins.qrscanner_mlkit">
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>

View File

@ -0,0 +1,85 @@
package com.yubico.authenticator.flutter_plugins.qrscanner_mlkit
import androidx.annotation.NonNull
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.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
class PermissionsResultRegistrar {
private var permissionsResultListener: PluginRegistry.RequestPermissionsResultListener? = null
fun setListener(listener: PluginRegistry.RequestPermissionsResultListener?) {
permissionsResultListener = listener
}
fun onResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
return permissionsResultListener?.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
) ?: false
}
}
/** QRScannerMLKitPlugin */
class QRScannerMLKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
private val registrar = PermissionsResultRegistrar()
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "qrscanner_mlkit")
channel.setMethodCallHandler(this)
binding.platformViewRegistry
.registerViewFactory(
"qrScannerNativeView",
QRScannerViewFactory(binding.binaryMessenger, registrar)
)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
}
override fun onDetachedFromActivity() {
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
return registrar.onResult(requestCode, permissions, grantResults)
}
}

View File

@ -0,0 +1,257 @@
package com.yubico.authenticator.flutter_plugins.qrscanner_mlkit
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Rect
import android.opengl.Visibility
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import org.json.JSONArray
import org.json.JSONObject
import org.w3c.dom.Text
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class QRScannerViewFactory(
private val binaryMessenger: BinaryMessenger,
private val permissionsResultRegistrar: PermissionsResultRegistrar
) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {
@Suppress("UNCHECKED_CAST")
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return QRScannerView(
context!!,
viewId,
binaryMessenger,
permissionsResultRegistrar,
creationParams
)
}
}
data class BarcodeInfo(val rawData: String, val rect: Rect)
typealias BarcodeAnalyzerListener = (Result<List<BarcodeInfo>>) -> Unit
internal class QRScannerView(
context: Context,
id: Int,
binaryMessenger: BinaryMessenger,
private val permissionsResultRegistrar: PermissionsResultRegistrar,
creationParams: Map<String?, Any?>?
) : PlatformView {
companion object {
const val TAG = "QRScannerView"
// permission related
const val PERMISSION_REQUEST_CODE = 1
private val PERMISSIONS_TO_REQUEST =
mutableListOf(
Manifest.permission.CAMERA,
).toTypedArray()
private const val CHANNEL_NAME = "com.yubico.authenticator.flutter_plugins.qr_scanner_channel"
}
private fun allPermissionsGranted(activity: Activity) = PERMISSIONS_TO_REQUEST.all {
ContextCompat.checkSelfPermission(
activity, it
) == PackageManager.PERMISSION_GRANTED
}
private fun requestPermissions(activity: Activity) {
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_TO_REQUEST,
PERMISSION_REQUEST_CODE
)
}
private val qrScannerView = LayoutInflater.from(context).inflate(R.layout.qr_scanner_view, null, false)
private val previewView = qrScannerView.findViewById<PreviewView>(R.id.preview_view).apply {
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
}
private val infoView = qrScannerView.findViewById<TextView>(R.id.text_info).apply {
setText(R.string.initializing)
}
private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
private var cameraProvider: ProcessCameraProvider? = null
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
private var imageAnalyzer: ImageAnalysis? = null
private var preview: Preview? = null
override fun getView(): View {
return qrScannerView
}
override fun dispose() {
cameraProvider?.unbindAll()
preview = null
imageAnalyzer = null
cameraExecutor.shutdown()
Log.d(TAG, "View disposed")
}
private val methodChannel: MethodChannel = MethodChannel(binaryMessenger, CHANNEL_NAME)
private var permissionsGranted = false
init {
if (context is Activity) {
permissionsGranted = allPermissionsGranted(context)
if (!permissionsGranted) {
previewView.visibility = View.GONE
infoView.setText(R.string.initializing)
permissionsResultRegistrar.setListener(
object : PluginRegistry.RequestPermissionsResultListener {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (permissions.size == 1 && grantResults.size == 1) {
if (permissions.first() == PERMISSIONS_TO_REQUEST.first() &&
grantResults.first() == PackageManager.PERMISSION_GRANTED
) {
bindUseCases(context)
} else {
infoView.setText(R.string.permissions_missing)
}
}
return true
}
return false
}
})
requestPermissions(context)
} else {
bindUseCases(context)
}
}
}
private fun bindUseCases(context: Context) {
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
cameraProvider?.unbindAll()
imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor, BarcodeAnalyzer { analyzeResult ->
if (analyzeResult.isSuccess) {
analyzeResult.getOrNull()?.let { result ->
val codes = JSONArray(
result.map { barcodeInfo ->
JSONObject(
mapOf(
"value" to barcodeInfo.rawData,
"location" to JSONArray(
listOf(
barcodeInfo.rect.left.toDouble(),
barcodeInfo.rect.top.toDouble(),
barcodeInfo.rect.right.toDouble(),
barcodeInfo.rect.bottom.toDouble()
)
)
)
)
}
)
methodChannel.invokeMethod("codes", codes.toString())
}
}
})
}
preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
cameraProvider?.bindToLifecycle(
context as LifecycleOwner,
cameraSelector,
preview, imageAnalyzer
)
previewView.visibility = View.VISIBLE
}, ContextCompat.getMainExecutor(context))
}
private class BarcodeAnalyzer(private val listener: BarcodeAnalyzerListener) :
ImageAnalysis.Analyzer {
private val barcodeScannerOptions = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
private val scanner = BarcodeScanning.getClient(barcodeScannerOptions)
override fun analyze(imageProxy: ImageProxy) {
@androidx.camera.core.ExperimentalGetImage
val mediaImage = imageProxy.image ?: return
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnFailureListener {
listener.invoke(Result.failure(it))
}
.addOnCompleteListener {
imageProxy.close()
if (it.isSuccessful) {
val result =
it.result.filter { barcode -> barcode.rawValue != null && barcode.boundingBox != null }
.map { barcode ->
BarcodeInfo(barcode.rawValue!!, barcode.boundingBox!!)
}
listener.invoke(Result.success(result))
}
}
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_info"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/background_dark"
android:gravity="center"
android:text="@string/initializing" />
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
</FrameLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="permissions_missing">Permissions missing</string>
<string name="initializing">Please wait…</string>
</resources>

View File

@ -0,0 +1,47 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -0,0 +1,16 @@
# qrscanner_mlkit_example
Demonstrates how to use the qrscanner_mlkit plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,71 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
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 flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.yubico.authenticator.flutter_plugins.qrscanner_mlkit_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yubico.authenticator.flutter_plugins.qrscanner_mlkit_example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yubico.authenticator.flutter_plugins.qrscanner_mlkit_example">
<application
android:label="qrscanner_mlkit_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package com.yubico.authenticator.flutter_plugins.qrscanner_mlkit_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yubico.authenticator.flutter_plugins.qrscanner_mlkit_example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@ -0,0 +1,62 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _qrscannerMlkitPlugin = QRScannerMLKit();
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion = await _qrscannerMlkitPlugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion\n'),
),
),
);
}
}

View File

@ -0,0 +1,182 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
qrscanner_mlkit:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
sdks:
dart: ">=2.17.0-266.1.beta <3.0.0"
flutter: ">=2.5.0"

View File

@ -0,0 +1,24 @@
name: qrscanner_mlkit_example
description: Demonstrates how to use the qrscanner_mlkit plugin.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=2.17.0-266.1.beta <3.0.0"
dependencies:
flutter:
sdk: flutter
qrscanner_mlkit:
path: ../
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true

View File

@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. 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.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../lib/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) =>
widget is Text && widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@ -0,0 +1,7 @@
import 'qrscanner_mlkit_platform_interface.dart';
class QRScannerMLKit {
Future<String?> getPlatformVersion() {
return QRScannerMLKitPlatform.instance.getPlatformVersion();
}
}

View File

@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'qrscanner_mlkit_platform_interface.dart';
/// An implementation of [QRScannerMLKitPlatform] that uses method channels.
class MethodChannelQRScannerMLKit extends QRScannerMLKitPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('qrscanner_mlkit');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

View File

@ -0,0 +1,29 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'qrscanner_mlkit_method_channel.dart';
abstract class QRScannerMLKitPlatform extends PlatformInterface {
/// Constructs a QRScannerMLKitPlatform.
QRScannerMLKitPlatform() : super(token: _token);
static final Object _token = Object();
static QRScannerMLKitPlatform _instance = MethodChannelQRScannerMLKit();
/// The default instance of [QRScannerMLKitPlatform] to use.
///
/// Defaults to [MethodChannelQRScannerMLKit].
static QRScannerMLKitPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [QRScannerMLKitPlatform] when
/// they register themselves.
static set instance(QRScannerMLKitPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

View File

@ -0,0 +1,70 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ScannedData {
final String data;
final Rect location;
ScannedData(this.data, this.location);
}
class QrScannerMLKitView extends StatefulWidget {
final Function(ScannedData data) onDetect;
const QrScannerMLKitView({Key? key, required this.onDetect})
: super(key: key);
@override
QrScannerMLKitViewState createState() => QrScannerMLKitViewState();
}
class QrScannerMLKitViewState extends State<QrScannerMLKitView> {
final MethodChannel channel = const MethodChannel(
"com.yubico.authenticator.flutter_plugins.qr_scanner_channel");
QrScannerMLKitViewState() : super() {
channel.setMethodCallHandler((call) async {
try {
var barcodes = jsonDecode(call.arguments);
if (barcodes is List && barcodes.isNotEmpty) {
var firstBarcode = barcodes[0];
var rawValue = firstBarcode["value"];
var location = firstBarcode["location"];
double l = location[0].toDouble();
double t = location[1].toDouble();
double r = location[2].toDouble();
double b = location[3].toDouble();
var locationRect = Rect.fromLTRB(l, t, r, b);
widget.onDetect(ScannedData(rawValue, locationRect));
}
} catch (e) {
print("Exception on receive: $e");
}
});
}
@override
void dispose() {
super.dispose();
channel.setMethodCallHandler(null);
}
@override
Widget build(BuildContext context) {
if (kDebugMode) {
print("Building QrScannerView");
}
const String viewType = 'qrScannerNativeView';
final Map<String, dynamic> creationParams = <String, dynamic>{};
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
}

View File

@ -0,0 +1,25 @@
name: qrscanner_mlkit
description: Android-only QR Scanner plugin based on ML Kit and CameraX API's
version: 1.0.0
homepage:
environment:
sdk: ">=2.17.0-266.1.beta <3.0.0"
flutter: ">=2.5.0"
dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
plugin:
platforms:
android:
package: com.yubico.authenticator.flutter_plugins.qrscanner_mlkit
pluginClass: QRScannerMLKitPlugin

View File

@ -0,0 +1,24 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit_method_channel.dart';
void main() {
MethodChannelQRScannerMLKit platform = MethodChannelQRScannerMLKit();
const MethodChannel channel = MethodChannel('qrscanner_mlkit');
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

View File

@ -0,0 +1,26 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit_method_channel.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit_platform_interface.dart';
class MockQRScannerMLKitPlatform with MockPlatformInterfaceMixin implements QRScannerMLKitPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final QRScannerMLKitPlatform initialPlatform = QRScannerMLKitPlatform.instance;
test('$MethodChannelQRScannerMLKit is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelQRScannerMLKit>());
});
test('getPlatformVersion', () async {
QRScannerMLKit qrscannerMlkitPlugin = QRScannerMLKit();
MockQRScannerMLKitPlatform fakePlatform = MockQRScannerMLKitPlatform();
QRScannerMLKitPlatform.instance = fakePlatform;
expect(await qrscannerMlkitPlugin.getPlatformVersion(), '42');
});
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:qrscanner_mlkit/qrscanner_mlkit_view.dart';
import '../../app/navigation_service.dart';
import '../../oath/models.dart';
@ -29,13 +29,11 @@ class OverlayClipper extends CustomClipper<Path> {
}
class MobileScannerWrapper extends StatelessWidget {
final MobileScannerController controller;
final Function(Barcode barcode, MobileScannerArguments? args)? onDetect;
final Function(ScannedData) onDetect;
final _ScanStatus status;
const MobileScannerWrapper({
Key? key,
required this.controller,
required this.onDetect,
required this.status,
}) : super(key: key);
@ -55,11 +53,8 @@ class MobileScannerWrapper extends StatelessWidget {
height: size.width - 38);
return Stack(children: [
MobileScanner(
controller: controller,
allowDuplicates: true,
onDetect: (barcode, args) {
onDetect?.call(barcode, args);
QrScannerMLKitView(onDetect: (scannedData) {
onDetect.call(scannedData);
}),
ClipPath(
clipper: OverlayClipper(),
@ -108,9 +103,6 @@ class _QrScannerViewState extends State<QrScannerView> {
CredentialData? _credentialData;
_ScanStatus _status = _ScanStatus.looking;
final MobileScannerController _controller =
MobileScannerController(facing: CameraFacing.back, torchEnabled: false);
void setError() {
_credentialData = null;
_scannedString = null;
@ -129,13 +121,14 @@ class _QrScannerViewState extends State<QrScannerView> {
});
}
void handleResult(String? code, MobileScannerArguments? args) {
void handleResult(ScannedData scannedData) {
if (_status != _ScanStatus.looking) {
// on success and error ignore reported codes
return;
}
setState(() {
if (code != null) {
if (scannedData.data.isNotEmpty) {
var code = scannedData.data;
try {
var parsedCredential = CredentialData.fromUri(Uri.parse(code));
_credentialData = parsedCredential;
@ -175,10 +168,8 @@ class _QrScannerViewState extends State<QrScannerView> {
),
body: Stack(children: [
MobileScannerWrapper(
controller: _controller,
status: _status,
onDetect: (barcode, args) =>
handleResult(barcode.rawValue, args),
onDetect: (scannedData) => handleResult(scannedData),
),
Padding(
padding:

View File

@ -352,15 +352,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
mobile_scanner:
dependency: "direct main"
description:
path: "."
ref: just-android
resolved-ref: b5473703e1e9c845dfedf129d394d0535367c9e6
url: "https://github.com/AdamVe/mobile_scanner.git"
source: git
version: "1.0.0"
package_config:
dependency: transitive
description:
@ -445,6 +436,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
qrscanner_mlkit:
dependency: "direct main"
description:
path: "android/flutter_plugins/qrscanner_mlkit"
relative: true
source: path
version: "1.0.0"
riverpod:
dependency: transitive
description:
@ -661,5 +659,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.17.0-0 <3.0.0"
dart: ">=2.17.0-266.1.beta <3.0.0"
flutter: ">=2.8.0"

View File

@ -42,10 +42,8 @@ dependencies:
json_annotation: ^4.4.0
freezed_annotation: ^1.0.0
window_manager: ^0.2.0
mobile_scanner:
git:
url: https://github.com/AdamVe/mobile_scanner.git
ref: just-android
qrscanner_mlkit:
path: ./android/flutter_plugins/qrscanner_mlkit
desktop_drop: ^0.3.3
dev_dependencies: