This commit is contained in:
Adam Velebil 2022-04-27 14:10:40 +02:00
commit 7a115b812c
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
54 changed files with 1573 additions and 36 deletions

View File

@ -39,7 +39,7 @@ jobs:
run: flutter build windows
- name: Check generated files
run: git diff --exit-code
run: git diff -I '.*android.*flutter_plugins.*qrscanner_zxing' --exit-code
- name: Move .dll files
run: |

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_zxing
Android-only Flutter plugin for scanning QR Codes. Implemented with zxing 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,57 @@
group 'com.yubico.authenticator.flutter_plugins.qrscanner_zxing'
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 {
def camerax_version = "1.1.0-beta03"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
def zxing_version = "3.3.0"
implementation "com.google.zxing:core:${zxing_version}"
implementation "com.google.zxing:android-core:${zxing_version}"
}

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_zxing'

View File

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

View File

@ -0,0 +1,278 @@
package com.yubico.authenticator.flutter_plugins.qrscanner_zxing
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.camera.core.*
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.zxing.*
import com.google.zxing.common.HybridBinarizer
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.JSONObject
import java.nio.ByteBuffer
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.Result
import kotlin.math.min
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
)
}
}
typealias BarcodeAnalyzerListener = (Result<String>) -> Unit
internal class QRScannerView(
context: Context,
id: Int,
binaryMessenger: BinaryMessenger,
private val permissionsResultRegistrar: PermissionsResultRegistrar,
creationParams: Map<String?, Any?>?
) : PlatformView {
private val uiThreadHandler = Handler(Looper.getMainLooper())
private var marginPct: Double? = null
companion object {
const val TAG = "QRScannerView"
// permission related
const val PERMISSION_REQUEST_CODE = 1
private val PERMISSIONS_TO_REQUEST =
mutableListOf(
Manifest.permission.CAMERA,
).toTypedArray()
// view related
private const val QR_SCANNER_ASPECT_RATIO = AspectRatio.RATIO_4_3
// communication channel
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 = View.inflate(context, R.layout.qr_scanner_view, null)
private val previewView = qrScannerView.findViewById<PreviewView>(R.id.preview_view).also {
it.scaleType = PreviewView.ScaleType.FILL_CENTER
}
private val infoText = qrScannerView.findViewById<TextView>(R.id.info_text)
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 {
// read margin parameter
// only use it if it has reasonable value
if (creationParams?.get("margin") is Number) {
val marginValue = creationParams["margin"] as Number
if (marginValue.toDouble() > 0.0 && marginValue.toDouble() < 45) {
marginPct = marginValue.toDouble()
}
}
if (context is Activity) {
permissionsGranted = allPermissionsGranted(context)
if (!permissionsGranted) {
requestPermissionsFromUser(context)
} else {
bindUseCases(context)
}
}
}
private fun requestPermissionsFromUser(activity: Activity) {
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
) {
previewView.visibility = View.VISIBLE
infoText.visibility = View.GONE
bindUseCases(activity)
} else {
previewView.visibility = View.GONE
infoText.visibility = View.VISIBLE
infoText.setText(R.string.please_grant_permissions)
}
} else {
previewView.visibility = View.GONE
infoText.visibility = View.VISIBLE
infoText.setText(R.string.please_grant_permissions)
}
return true
}
return false
}
})
requestPermissions(activity)
}
private fun bindUseCases(context: Context) {
cameraProviderFuture.addListener({
previewView.visibility = View.VISIBLE
cameraProvider = cameraProviderFuture.get()
cameraProvider?.unbindAll()
imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetAspectRatio(QR_SCANNER_ASPECT_RATIO)
.build()
.also {
it.setAnalyzer(cameraExecutor, BarcodeAnalyzer(marginPct) { analyzeResult ->
if (analyzeResult.isSuccess) {
analyzeResult.getOrNull()?.let { result ->
uiThreadHandler.post {
methodChannel.invokeMethod(
"codeFound", JSONObject(
mapOf("value" to result)
).toString()
)
}
}
}
})
}
preview = Preview.Builder()
.setTargetAspectRatio(QR_SCANNER_ASPECT_RATIO)
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
cameraProvider?.bindToLifecycle(
context as LifecycleOwner,
cameraSelector,
preview, imageAnalyzer
)
}, ContextCompat.getMainExecutor(context))
}
private class BarcodeAnalyzer(
private val marginPct: Double?,
private val listener: BarcodeAnalyzerListener
) :
ImageAnalysis.Analyzer {
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
override fun analyze(imageProxy: ImageProxy) {
try {
val buffer = imageProxy.planes[0].buffer
val intArray = buffer.toByteArray().map { it.toInt() }.toIntArray()
val source: LuminanceSource =
RGBLuminanceSource(imageProxy.width, imageProxy.height, intArray)
val fullSize = BinaryBitmap(HybridBinarizer(source))
val bitmapToProcess = if (marginPct != null) {
val shorterDim = min(imageProxy.width, imageProxy.height)
val cropMargin = marginPct * 0.01 * shorterDim
val cropWH = shorterDim - 2.0 * cropMargin
val cropT = (imageProxy.height - cropWH) / 2.0
val cropL = (imageProxy.width - cropWH) / 2.0
fullSize.crop(
cropL.toInt(),
cropT.toInt(),
cropWH.toInt(),
cropWH.toInt()
)
} else {
fullSize
}
val reader = MultiFormatReader()
val result: com.google.zxing.Result = reader.decode(bitmapToProcess)
Log.d(TAG, "Result text: ${result.text}")
listener.invoke(Result.success(result.text))
} catch (_: NotFoundException) {
// ignored: no code was found
} finally {
// important call
imageProxy.close()
}
}
}
}

View File

@ -0,0 +1,85 @@
package com.yubico.authenticator.flutter_plugins.qrscanner_zxing
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
}
}
/** QRScannerZxingPlugin */
class QRScannerZxingPlugin : 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_zxing")
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,29 @@
<?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"
android:background="@android:color/background_dark">
<TextView
android:id="@+id/info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@android:color/black"
android:background="@android:color/background_light"
android:gravity="left"
android:textSize="12sp"
android:lineSpacingMultiplier="1.2"
android:layout_margin="32dp"
android:padding="16dp"
android:drawableStart="@android:drawable/ic_dialog_alert"
android:drawablePadding="16dp"
android:text="@string/please_grant_permissions" />
<androidx.camera.view.PreviewView
android:visibility="gone"
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="please_grant_permissions">The QR scanner cannot work properly because grant of Camera permissions is missing.\n\nPlease, fix this by changing the Camera permissions setting in the phone Settings.</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_zxing_example
Demonstrates how to use the qrscanner_zxing 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_zxing_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_zxing_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_zxing_example">
<application
android:label="qrscanner_zxing_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_zxing_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_zxing_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,60 @@
import 'dart:math';
import 'package:flutter/material.dart';
class CutoutOverlay extends StatelessWidget {
final int marginPct;
const CutoutOverlay({Key? key, required this.marginPct}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
child: CustomPaint(
painter: CutoutPainter(marginPct: marginPct),
));
}
}
class CutoutPainter extends CustomPainter {
final int marginPct;
CutoutPainter({required this.marginPct});
@override
void paint(Canvas canvas, Size size) {
final overlayPaint = Paint()..color = Colors.black54;
final strokePaint = Paint()
..color = Colors.green
..style = PaintingStyle.stroke
..strokeWidth = 2;
final shorterDim = min(size.width, size.height);
final cutoutWidth = shorterDim - (2 * shorterDim * marginPct / 100);
final cutoutRect = Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: cutoutWidth,
height: cutoutWidth,
);
final cutOutRRect =
RRect.fromRectAndRadius(cutoutRect, const Radius.circular(5.0));
final targetPath = Path()..addRRect(cutOutRRect);
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)),
targetPath..close(),
),
overlayPaint);
canvas.drawPath(targetPath, strokePaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

View File

@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:qrscanner_zxing/qrscanner_zxing_view.dart';
import 'cutout_overlay.dart';
void main() {
runApp(const QRCodeScannerExampleApp());
}
class QRCodeScannerExampleApp extends StatelessWidget {
const QRCodeScannerExampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QR Scanner Example',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: const AppHomePage(title: 'QR Scanner Example'),
);
}
}
class AppHomePage extends StatelessWidget {
const AppHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => const QRScannerPage(),
transitionDuration: const Duration(seconds: 0),
reverseTransitionDuration: const Duration(seconds: 0),
));
},
child: const Text("Open QR Scanner")),
],
),
),
);
}
}
class QRScannerPage extends StatefulWidget {
const QRScannerPage({Key? key}) : super(key: key);
@override
QRScannerPageState createState() => QRScannerPageState();
}
class QRScannerPageState extends State<QRScannerPage> {
String? currentCode;
QRScannerPageState({Key? key}) : super();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: QRScannerZxingView(
marginPct: 10,
onDetect: (result) {
if (currentCode == null) {
setState(() {
currentCode = result;
});
}
})),
const Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: CutoutOverlay(
marginPct: 5,
)),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Back'),
),
ElevatedButton.icon(
icon: const Icon(Icons.refresh),
label: const Text("Again"),
onPressed: () {
setState(() {
currentCode = null;
});
},
),
],
),
),
),
Positioned(
bottom: 80,
left: 0,
right: 0,
child: Card(
margin: const EdgeInsets.all(20),
elevation: 100,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text("Found QR code:"),
Text(currentCode ?? "NO CODE DETECTED"),
],
),
)),
),
Positioned(
top: 50,
left: 0,
right: 0,
child: Card(
margin: const EdgeInsets.all(20),
elevation: 100,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: const [
Text("QR scanner example"),
],
),
)),
)
],
));
}
}

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_zxing:
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_zxing_example
description: Demonstrates how to use the qrscanner_zxing 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_zxing:
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,7 @@
import 'qrscanner_zxing_platform_interface.dart';
class QRScannerZxing {
Future<String?> getPlatformVersion() {
return QRScannerZxingPlatform.instance.getPlatformVersion();
}
}

View File

@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'qrscanner_zxing_platform_interface.dart';
/// An implementation of [QRScannerZxingPlatform] that uses method channels.
class MethodChannelQRScannerZxing extends QRScannerZxingPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('qrscanner_zxing');
@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_zxing_method_channel.dart';
abstract class QRScannerZxingPlatform extends PlatformInterface {
/// Constructs a QRScannerZxingPlatform.
QRScannerZxingPlatform() : super(token: _token);
static final Object _token = Object();
static QRScannerZxingPlatform _instance = MethodChannelQRScannerZxing();
/// The default instance of [QRScannerZxingPlatform] to use.
///
/// Defaults to [MethodChannelQRScannerZxing].
static QRScannerZxingPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [QRScannerZxingPlatform] when
/// they register themselves.
static set instance(QRScannerZxingPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

View File

@ -0,0 +1,80 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
class QRScannerZxingView extends StatefulWidget {
final int marginPct;
final Function(String rawData) onDetect;
const QRScannerZxingView(
{Key? key, required this.marginPct, required this.onDetect})
: super(key: key);
@override
QRScannerZxingViewState createState() => QRScannerZxingViewState();
}
class QRScannerZxingViewState extends State<QRScannerZxingView> {
final MethodChannel channel = const MethodChannel(
"com.yubico.authenticator.flutter_plugins.qr_scanner_channel");
QRScannerZxingViewState() : super() {
channel.setMethodCallHandler((call) async {
try {
switch (call.method) {
case "codeFound":
var arguments = jsonDecode(call.arguments);
var rawValue = arguments["value"];
widget.onDetect(rawValue);
return;
}
} catch (e) {
if (kDebugMode) {
debugPrint("Exception in onDetect: $e}");
}
}
});
}
@override
void dispose() {
super.dispose();
channel.setMethodCallHandler(null);
}
@override
Widget build(BuildContext context) {
const String viewType = 'qrScannerNativeView';
Map<String, dynamic> creationParams = <String, dynamic>{
"margin": widget.marginPct
};
return PlatformViewLink(
viewType: viewType,
surfaceFactory:
(BuildContext context, PlatformViewController controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initExpensiveAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
});
}
}

View File

@ -0,0 +1,25 @@
name: qrscanner_zxing
description: Android-only QR Scanner plugin based on zxing 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_zxing
pluginClass: QRScannerZxingPlugin

View File

@ -0,0 +1,24 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
void main() {
MethodChannelQRScannerZxing platform = MethodChannelQRScannerZxing();
const MethodChannel channel = MethodChannel('qrscanner_zxing');
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,29 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:qrscanner_zxing/qrscanner_zxing.dart';
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
import 'package:qrscanner_zxing/qrscanner_zxing_platform_interface.dart';
class MockQRScannerZxingPlatform
with MockPlatformInterfaceMixin
implements QRScannerZxingPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final QRScannerZxingPlatform initialPlatform =
QRScannerZxingPlatform.instance;
test('$MethodChannelQRScannerZxing is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelQRScannerZxing>());
});
test('getPlatformVersion', () async {
QRScannerZxing qrscannerZxingPlugin = QRScannerZxing();
MockQRScannerZxingPlatform fakePlatform = MockQRScannerZxingPlatform();
QRScannerZxingPlatform.instance = fakePlatform;
expect(await qrscannerZxingPlugin.getPlatformVersion(), '42');
});
}

View File

@ -12,8 +12,10 @@ class AndroidQrScanner implements QrScanner {
Future<String> scanQr([String? _]) async {
var code =
await Navigator.of(NavigationService.navigatorKey.currentContext!)
.push(MaterialPageRoute(
builder: (context) => const QrScannerView(),
.push(PageRouteBuilder(
pageBuilder: (_, __, ___) => const QrScannerView(),
transitionDuration: const Duration(seconds: 0),
reverseTransitionDuration: const Duration(seconds: 0),
));
return code;

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:qrscanner_zxing/qrscanner_zxing_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(String) onDetect;
final _ScanStatus status;
const MobileScannerWrapper({
Key? key,
required this.controller,
required this.onDetect,
required this.status,
}) : super(key: key);
@ -55,11 +53,10 @@ class MobileScannerWrapper extends StatelessWidget {
height: size.width - 38);
return Stack(children: [
MobileScanner(
controller: controller,
allowDuplicates: true,
onDetect: (barcode, args) {
onDetect?.call(barcode, args);
QRScannerZxingView(
marginPct: 20,
onDetect: (barCode) {
onDetect.call(barCode);
}),
ClipPath(
clipper: OverlayClipper(),
@ -108,9 +105,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,17 +123,17 @@ class _QrScannerViewState extends State<QrScannerView> {
});
}
void handleResult(String? code, MobileScannerArguments? args) {
void handleResult(String barCode) {
if (_status != _ScanStatus.looking) {
// on success and error ignore reported codes
return;
}
setState(() {
if (code != null) {
if (barCode.isNotEmpty) {
try {
var parsedCredential = CredentialData.fromUri(Uri.parse(code));
var parsedCredential = CredentialData.fromUri(Uri.parse(barCode));
_credentialData = parsedCredential;
_scannedString = code;
_scannedString = barCode;
_status = _ScanStatus.success;
Future.delayed(const Duration(milliseconds: 800), () {
@ -175,10 +169,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_zxing:
dependency: "direct main"
description:
path: "android/flutter_plugins/qrscanner_zxing"
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_zxing:
path: android/flutter_plugins/qrscanner_zxing
desktop_drop: ^0.3.3
dev_dependencies: