mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-28 20:06:56 +03:00
support scanning from image data
This commit is contained in:
parent
4ffda92872
commit
3b766fb28f
@ -36,7 +36,9 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.google.zxing.*
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.NotFoundException
|
||||||
|
import com.google.zxing.RGBLuminanceSource
|
||||||
import com.google.zxing.common.HybridBinarizer
|
import com.google.zxing.common.HybridBinarizer
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
@ -290,11 +292,6 @@ internal class QRScannerView(
|
|||||||
) : ImageAnalysis.Analyzer {
|
) : ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
var analysisPaused = false
|
var analysisPaused = false
|
||||||
|
|
||||||
val multiFormatReader = MultiFormatReader().also {
|
|
||||||
it.setHints(mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var analyzedImagesCount = 0
|
var analyzedImagesCount = 0
|
||||||
|
|
||||||
private fun ByteBuffer.toByteArray(lastRowPadding: Int): ByteArray {
|
private fun ByteBuffer.toByteArray(lastRowPadding: Int): ByteArray {
|
||||||
@ -392,14 +389,14 @@ internal class QRScannerView(
|
|||||||
fullSize
|
fullSize
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: com.google.zxing.Result = multiFormatReader.decode(bitmapToProcess)
|
val result = QrCodeScanner.scan(bitmapToProcess)
|
||||||
if (analysisPaused) {
|
if (analysisPaused) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
analysisPaused = true // pause
|
analysisPaused = true // pause
|
||||||
Log.v(TAG, "Analysis result: ${result.text}")
|
Log.v(TAG, "Analysis result: $result")
|
||||||
listener.invoke(Result.success(result.text))
|
listener.invoke(Result.success(result))
|
||||||
} catch (_: NotFoundException) {
|
} catch (_: NotFoundException) {
|
||||||
if (analyzedImagesCount == 0) {
|
if (analyzedImagesCount == 0) {
|
||||||
Log.v(TAG, " No QR code found (NotFoundException)")
|
Log.v(TAG, " No QR code found (NotFoundException)")
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
|
|
||||||
package com.yubico.authenticator.flutter_plugins.qrscanner_zxing
|
package com.yubico.authenticator.flutter_plugins.qrscanner_zxing
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.RGBLuminanceSource
|
||||||
|
import com.google.zxing.common.HybridBinarizer
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||||
@ -24,6 +30,7 @@ import io.flutter.plugin.common.MethodChannel
|
|||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
class PermissionsResultRegistrar {
|
class PermissionsResultRegistrar {
|
||||||
|
|
||||||
@ -65,10 +72,48 @@ class QRScannerZxingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||||
if (call.method == "getPlatformVersion") {
|
when (call.method) {
|
||||||
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
"getPlatformVersion" -> {
|
||||||
} else {
|
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
||||||
result.notImplemented()
|
}
|
||||||
|
"scanBitmap" -> {
|
||||||
|
val imageFileData = call.argument<ByteArray>("bitmap")
|
||||||
|
var bitmap: Bitmap? = null
|
||||||
|
try {
|
||||||
|
imageFileData?.let { byteArray ->
|
||||||
|
Log.i(TAG, "Received ${byteArray.size} bytes")
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
options.inSampleSize = 4
|
||||||
|
bitmap = BitmapFactory.decodeByteArray(imageFileData, 0, byteArray.size, options)
|
||||||
|
bitmap?.let {
|
||||||
|
val intArray = IntArray(it.allocationByteCount)
|
||||||
|
it.getPixels(intArray, 0, it.width, 0, 0, it.width, it.height)
|
||||||
|
val luminanceSource =
|
||||||
|
RGBLuminanceSource(it.rowBytes, it.height, intArray)
|
||||||
|
val binaryBitmap = BinaryBitmap(HybridBinarizer(luminanceSource))
|
||||||
|
val scanResult = QrCodeScanner.scan(binaryBitmap)
|
||||||
|
Log.i(TAG, "Scan result: $scanResult")
|
||||||
|
result.success(scanResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failure decoding data: $e")
|
||||||
|
result.error("Failed to decode", e.message, e)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
bitmap?.let {
|
||||||
|
it.recycle()
|
||||||
|
bitmap = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "Failure decoding data: Invalid image format ")
|
||||||
|
result.error("Failed to decode", "Invalid image format", null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,4 +142,8 @@ class QRScannerZxingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
return registrar.onResult(requestCode, permissions, grantResults)
|
return registrar.onResult(requestCode, permissions, grantResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "QRScannerZxPlugin"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.yubico.authenticator.flutter_plugins.qrscanner_zxing
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.DecodeHintType
|
||||||
|
import com.google.zxing.MultiFormatReader
|
||||||
|
|
||||||
|
object QrCodeScanner {
|
||||||
|
|
||||||
|
private val qrCodeScanner = MultiFormatReader().also {
|
||||||
|
it.setHints(mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scan(binaryBitmap: BinaryBitmap) : String {
|
||||||
|
val result: com.google.zxing.Result = qrCodeScanner.decode(binaryBitmap)
|
||||||
|
return result.text
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,11 @@ apply plugin: 'kotlin-android'
|
|||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdk 34
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
namespace 'com.yubico.authenticator.flutter_plugins.qrscanner_zxing_example'
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
@ -48,7 +50,7 @@ android {
|
|||||||
// You can update the following values to match your application needs.
|
// 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.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
@ -60,6 +62,10 @@ android {
|
|||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.21'
|
ext.kotlin_version = '1.9.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
classpath 'com.android.tools.build:gradle:8.1.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,6 +26,6 @@ subprojects {
|
|||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
tasks.register('clean', Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
|
||||||
import 'package:qrscanner_zxing/qrscanner_zxing_view.dart';
|
import 'package:qrscanner_zxing/qrscanner_zxing_view.dart';
|
||||||
|
|
||||||
import 'cutout_overlay.dart';
|
import 'cutout_overlay.dart';
|
||||||
@ -64,6 +66,32 @@ class AppHomePage extends StatelessWidget {
|
|||||||
));
|
));
|
||||||
},
|
},
|
||||||
child: const Text("Open QR Scanner")),
|
child: const Text("Open QR Scanner")),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await FilePicker.platform.pickFiles(
|
||||||
|
allowedExtensions: ['png', 'jpg'],
|
||||||
|
type: FileType.custom,
|
||||||
|
allowMultiple: false,
|
||||||
|
lockParentWindow: true,
|
||||||
|
withData: true,
|
||||||
|
dialogTitle: 'Select file with QR code');
|
||||||
|
|
||||||
|
if (result == null || !result.isSinglePick) {
|
||||||
|
// no result
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bytes = result.files.first.bytes;
|
||||||
|
|
||||||
|
if (bytes != null) {
|
||||||
|
var channel = MethodChannelQRScannerZxing();
|
||||||
|
var value = await channel.scanBitmap(bytes);
|
||||||
|
debugPrint(value);
|
||||||
|
} else {
|
||||||
|
// no files selected
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text("Scan from file")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -37,10 +37,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.1"
|
version: "1.17.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -57,6 +57,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.5.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -70,19 +86,24 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.16"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
js:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description: flutter
|
||||||
name: js
|
source: sdk
|
||||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
version: "0.0.0"
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.7"
|
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -95,18 +116,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.15"
|
version: "0.12.16"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.5.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -127,10 +148,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.6"
|
||||||
qrscanner_zxing:
|
qrscanner_zxing:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -147,10 +168,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -187,10 +208,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.6.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -199,6 +220,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4-beta"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.9"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0-0 <4.0.0"
|
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||||
flutter: ">=2.5.0"
|
flutter: ">=3.7.0"
|
||||||
|
@ -4,7 +4,7 @@ description: Demonstrates how to use the qrscanner_zxing plugin.
|
|||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0-266.1.beta <3.0.0"
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@ -14,6 +14,7 @@ dependencies:
|
|||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
file_picker: ^5.3.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -14,10 +14,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'qrscanner_zxing_platform_interface.dart';
|
import 'qrscanner_zxing_platform_interface.dart';
|
||||||
|
|
||||||
class QRScannerZxing {
|
class QRScannerZxing {
|
||||||
Future<String?> getPlatformVersion() {
|
Future<String?> getPlatformVersion() {
|
||||||
return QRScannerZxingPlatform.instance.getPlatformVersion();
|
return QRScannerZxingPlatform.instance.getPlatformVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> scanBitmap(Uint8List bitmap) {
|
||||||
|
return QRScannerZxingPlatform.instance.scanBitmap(bitmap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,4 +31,11 @@ class MethodChannelQRScannerZxing extends QRScannerZxingPlatform {
|
|||||||
await methodChannel.invokeMethod<String>('getPlatformVersion');
|
await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> scanBitmap(Uint8List bitmap) async {
|
||||||
|
final version = await methodChannel
|
||||||
|
.invokeMethod<String>('scanBitmap', {'bitmap': bitmap});
|
||||||
|
return version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||||
|
|
||||||
import 'qrscanner_zxing_method_channel.dart';
|
import 'qrscanner_zxing_method_channel.dart';
|
||||||
@ -42,4 +44,8 @@ abstract class QRScannerZxingPlatform extends PlatformInterface {
|
|||||||
Future<String?> getPlatformVersion() {
|
Future<String?> getPlatformVersion() {
|
||||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> scanBitmap(Uint8List bitmap) {
|
||||||
|
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||||
import 'package:qrscanner_zxing/qrscanner_zxing.dart';
|
import 'package:qrscanner_zxing/qrscanner_zxing.dart';
|
||||||
@ -25,6 +27,10 @@ class MockQRScannerZxingPlatform
|
|||||||
implements QRScannerZxingPlatform {
|
implements QRScannerZxingPlatform {
|
||||||
@override
|
@override
|
||||||
Future<String?> getPlatformVersion() => Future.value('42');
|
Future<String?> getPlatformVersion() => Future.value('42');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> scanBitmap(Uint8List bitmap) =>
|
||||||
|
Future.value(bitmap.length.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -42,4 +48,12 @@ void main() {
|
|||||||
|
|
||||||
expect(await qrscannerZxingPlugin.getPlatformVersion(), '42');
|
expect(await qrscannerZxingPlugin.getPlatformVersion(), '42');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('scanBitmap', () async {
|
||||||
|
QRScannerZxing qrscannerZxingPlugin = QRScannerZxing();
|
||||||
|
MockQRScannerZxingPlatform fakePlatform = MockQRScannerZxingPlatform();
|
||||||
|
QRScannerZxingPlatform.instance = fakePlatform;
|
||||||
|
|
||||||
|
expect(await qrscannerZxingPlugin.scanBitmap(Uint8List(10)), '10');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ const _prefix = 'android.keys';
|
|||||||
|
|
||||||
const okButton = Key('$_prefix.ok');
|
const okButton = Key('$_prefix.ok');
|
||||||
const manualEntryButton = Key('$_prefix.manual_entry');
|
const manualEntryButton = Key('$_prefix.manual_entry');
|
||||||
|
const readFromImage = Key('$_prefix.read_image_file');
|
||||||
|
|
||||||
const nfcBypassTouchSetting = Key('$_prefix.nfc_bypass_touch');
|
const nfcBypassTouchSetting = Key('$_prefix.nfc_bypass_touch');
|
||||||
const nfcSilenceSoundsSettings = Key('$_prefix.nfc_silence_sounds');
|
const nfcSilenceSoundsSettings = Key('$_prefix.nfc_silence_sounds');
|
||||||
|
@ -14,39 +14,50 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:yubico_authenticator/app/state.dart';
|
import 'package:yubico_authenticator/app/state.dart';
|
||||||
import 'package:yubico_authenticator/exception/cancellation_exception.dart';
|
import 'package:yubico_authenticator/exception/cancellation_exception.dart';
|
||||||
import 'package:yubico_authenticator/theme.dart';
|
import 'package:yubico_authenticator/theme.dart';
|
||||||
|
|
||||||
|
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
|
||||||
|
|
||||||
import 'qr_scanner_view.dart';
|
import 'qr_scanner_view.dart';
|
||||||
|
|
||||||
class AndroidQrScanner implements QrScanner {
|
class AndroidQrScanner implements QrScanner {
|
||||||
final WithContext _withContext;
|
final WithContext _withContext;
|
||||||
|
|
||||||
AndroidQrScanner(this._withContext);
|
AndroidQrScanner(this._withContext);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> scanQr([String? _]) async {
|
Future<String?> scanQr([String? imageData]) async {
|
||||||
var scannedCode = await _withContext(
|
if (imageData == null) {
|
||||||
(context) async => await Navigator.of(context).push(PageRouteBuilder(
|
var scannedCode = await _withContext(
|
||||||
pageBuilder: (_, __, ___) =>
|
(context) async =>
|
||||||
Theme(data: AppTheme.darkTheme, child: const QrScannerView()),
|
await Navigator.of(context).push(PageRouteBuilder(
|
||||||
settings: const RouteSettings(name: 'android_qr_scanner_view'),
|
pageBuilder: (_, __, ___) =>
|
||||||
transitionDuration: const Duration(seconds: 0),
|
Theme(data: AppTheme.darkTheme, child: const QrScannerView()),
|
||||||
reverseTransitionDuration: const Duration(seconds: 0),
|
settings: const RouteSettings(name: 'android_qr_scanner_view'),
|
||||||
)));
|
transitionDuration: const Duration(seconds: 0),
|
||||||
if (scannedCode == null) {
|
reverseTransitionDuration: const Duration(seconds: 0),
|
||||||
// user has cancelled the scan
|
)));
|
||||||
throw CancellationException();
|
if (scannedCode == null) {
|
||||||
|
// user has cancelled the scan
|
||||||
|
throw CancellationException();
|
||||||
|
}
|
||||||
|
if (scannedCode == '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return scannedCode;
|
||||||
|
} else {
|
||||||
|
var zxingChannel = MethodChannelQRScannerZxing();
|
||||||
|
return await zxingChannel.scanBitmap(base64Decode(imageData));
|
||||||
}
|
}
|
||||||
if (scannedCode == '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return scannedCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QrScanner? Function(dynamic) androidQrScannerProvider(hasCamera) {
|
QrScanner? Function(dynamic) androidQrScannerProvider(hasCamera) {
|
||||||
return (ref) =>
|
return (ref) =>
|
||||||
hasCamera ? AndroidQrScanner(ref.watch(withContextProvider)) : null;
|
hasCamera ? AndroidQrScanner(ref.watch(withContextProvider)) : null;
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:yubico_authenticator/app/state.dart';
|
||||||
|
|
||||||
import '../keys.dart' as keys;
|
import '../keys.dart' as keys;
|
||||||
import 'qr_scanner_scan_status.dart';
|
import 'qr_scanner_scan_status.dart';
|
||||||
|
|
||||||
class QRScannerUI extends StatelessWidget {
|
class QRScannerUI extends ConsumerWidget {
|
||||||
final ScanStatus status;
|
final ScanStatus status;
|
||||||
final Size screenSize;
|
final Size screenSize;
|
||||||
final GlobalKey overlayWidgetKey;
|
final GlobalKey overlayWidgetKey;
|
||||||
@ -32,7 +37,7 @@ class QRScannerUI extends StatelessWidget {
|
|||||||
required this.overlayWidgetKey});
|
required this.overlayWidgetKey});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
@ -73,15 +78,51 @@ class QRScannerUI extends StatelessWidget {
|
|||||||
textScaleFactor: 0.7,
|
textScaleFactor: 0.7,
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
OutlinedButton(
|
Row(
|
||||||
onPressed: () {
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
Navigator.of(context).pop('');
|
children: [
|
||||||
},
|
OutlinedButton(
|
||||||
key: keys.manualEntryButton,
|
onPressed: () {
|
||||||
child: Text(
|
Navigator.of(context).pop('');
|
||||||
l10n.s_enter_manually,
|
},
|
||||||
style: const TextStyle(color: Colors.white),
|
key: keys.manualEntryButton,
|
||||||
)),
|
child: Text(
|
||||||
|
l10n.s_enter_manually,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
)),
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop('');
|
||||||
|
final result = await FilePicker.platform.pickFiles(
|
||||||
|
allowedExtensions: ['png', 'jpg'],
|
||||||
|
type: FileType.custom,
|
||||||
|
allowMultiple: false,
|
||||||
|
lockParentWindow: true,
|
||||||
|
dialogTitle: 'Select file with QR code');
|
||||||
|
if (result != null && result.files.isNotEmpty) {
|
||||||
|
final fileWithCode = result.files.first;
|
||||||
|
final bytes = fileWithCode.bytes;
|
||||||
|
if (bytes == null || bytes.isEmpty) {
|
||||||
|
//err return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bytes.length > 3 * 1024 * 1024) {
|
||||||
|
// too big file
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final scanner = ref.read(qrScannerProvider);
|
||||||
|
if (scanner != null) {
|
||||||
|
await scanner.scanQr(base64UrlEncode(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: keys.readFromImage,
|
||||||
|
child: Text(
|
||||||
|
l10n.s_read_from_image,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8)
|
const SizedBox(height: 8)
|
||||||
|
@ -516,6 +516,7 @@
|
|||||||
"q_want_to_scan": "Would like to scan?",
|
"q_want_to_scan": "Would like to scan?",
|
||||||
"q_no_qr": "No QR code?",
|
"q_no_qr": "No QR code?",
|
||||||
"s_enter_manually": "Enter manually",
|
"s_enter_manually": "Enter manually",
|
||||||
|
"s_read_from_image": "Provide image file",
|
||||||
|
|
||||||
"@_factory_reset": {},
|
"@_factory_reset": {},
|
||||||
"s_reset": "Reset",
|
"s_reset": "Reset",
|
||||||
|
Loading…
Reference in New Issue
Block a user