86 select camera on startflutter-beta
| @@ -63,7 +63,6 @@ class FlutterQrPlugin : FlutterPlugin, ActivityAware { | |||
| inner class CameraRequestPermissionsListener : PluginRegistry.RequestPermissionsResultListener { | |||
| override fun onRequestPermissionsResult(id: Int, permissions: Array<String>, grantResults: IntArray): Boolean { | |||
| if (id == Shared.CAMERA_REQUEST_ID && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | |||
| Shared.cameraPermissionContinuation?.run() | |||
| return true | |||
| } | |||
| return false | |||
| @@ -3,13 +3,13 @@ package net.touchcapture.qr.flutterqr | |||
| import android.Manifest | |||
| import android.app.Activity | |||
| import android.app.Application | |||
| import android.content.Context | |||
| import android.content.pm.PackageManager | |||
| import android.os.Bundle | |||
| import android.view.View | |||
| import com.google.zxing.ResultPoint | |||
| import android.hardware.Camera.CameraInfo | |||
| import android.os.Build | |||
| import com.google.zxing.BarcodeFormat | |||
| import com.journeyapps.barcodescanner.BarcodeCallback | |||
| import com.journeyapps.barcodescanner.BarcodeResult | |||
| import com.journeyapps.barcodescanner.BarcodeView | |||
| @@ -17,12 +17,11 @@ import io.flutter.plugin.common.BinaryMessenger | |||
| import io.flutter.plugin.common.MethodCall | |||
| import io.flutter.plugin.common.MethodChannel | |||
| import io.flutter.plugin.platform.PlatformView | |||
| class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) : | |||
| class QRView(messenger: BinaryMessenger, id: Int, private val params: HashMap<String, Any>) : | |||
| PlatformView, MethodChannel.MethodCallHandler { | |||
| private var isTorchOn: Boolean = false | |||
| private var barcodeView: BarcodeView? = null | |||
| private var requestingPermission = false | |||
| private val channel: MethodChannel | |||
| init { | |||
| @@ -65,89 +64,173 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) | |||
| } | |||
| override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { | |||
| when(call.method){ | |||
| "flipCamera" -> { | |||
| flipCamera() | |||
| } | |||
| "toggleFlash" -> { | |||
| toggleFlash() | |||
| } | |||
| "pauseCamera" -> { | |||
| pauseCamera() | |||
| } | |||
| "resumeCamera" -> { | |||
| resumeCamera() | |||
| } | |||
| when(call.method) { | |||
| "startScan" -> startScan(call.arguments as? List<Int>, result) | |||
| "stopScan" -> stopScan() | |||
| "flipCamera" -> flipCamera(result) | |||
| "toggleFlash" -> toggleFlash(result) | |||
| "pauseCamera" -> pauseCamera(result) | |||
| "resumeCamera" -> resumeCamera(result) | |||
| "requestPermissions" -> checkAndRequestPermission(result) | |||
| "getCameraInfo" -> getCameraInfo(result) | |||
| "getFlashInfo" -> getFlashInfo(result) | |||
| "getSystemFeatures" -> getSystemFeatures(result) | |||
| else -> result.notImplemented() | |||
| } | |||
| } | |||
| private fun flipCamera() { | |||
| barcodeView?.pause() | |||
| val settings = barcodeView?.cameraSettings | |||
| private fun getCameraInfo(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| result.success(barcodeView!!.cameraSettings.requestedCameraId) | |||
| } | |||
| private fun flipCamera(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| barcodeView!!.pause() | |||
| val settings = barcodeView!!.cameraSettings | |||
| if(settings?.requestedCameraId == CameraInfo.CAMERA_FACING_FRONT) | |||
| if(settings.requestedCameraId == CameraInfo.CAMERA_FACING_FRONT) | |||
| settings.requestedCameraId = CameraInfo.CAMERA_FACING_BACK | |||
| else | |||
| settings?.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT | |||
| settings.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT | |||
| barcodeView?.cameraSettings = settings | |||
| barcodeView?.resume() | |||
| barcodeView!!.cameraSettings = settings | |||
| barcodeView!!.resume() | |||
| result.success(settings.requestedCameraId) | |||
| } | |||
| private fun toggleFlash() { | |||
| private fun getFlashInfo(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| result.success(isTorchOn) | |||
| } | |||
| private fun toggleFlash(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| if (hasFlash()) { | |||
| barcodeView?.setTorch(!isTorchOn) | |||
| barcodeView!!.setTorch(!isTorchOn) | |||
| isTorchOn = !isTorchOn | |||
| result.success(isTorchOn) | |||
| } else { | |||
| result.error("404", "This device doesn't support flash", null) | |||
| } | |||
| } | |||
| private fun pauseCamera() { | |||
| private fun pauseCamera(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| if (barcodeView!!.isPreviewActive) { | |||
| barcodeView?.pause() | |||
| barcodeView!!.pause() | |||
| } | |||
| result.success(true) | |||
| } | |||
| private fun resumeCamera() { | |||
| private fun resumeCamera(result: MethodChannel.Result) { | |||
| if (barcodeView == null) { | |||
| return barCodeViewNotSet(result) | |||
| } | |||
| if (!barcodeView!!.isPreviewActive) { | |||
| barcodeView?.resume() | |||
| barcodeView!!.resume() | |||
| } | |||
| result.success(true) | |||
| } | |||
| private fun hasFlash(): Boolean { | |||
| return context.packageManager | |||
| .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) | |||
| return hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) | |||
| } | |||
| private fun hasBackCamera(): Boolean { | |||
| return hasSystemFeature(PackageManager.FEATURE_CAMERA) | |||
| } | |||
| private fun hasFrontCamera(): Boolean { | |||
| return hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT) | |||
| } | |||
| private fun hasSystemFeature(feature: String): Boolean { | |||
| return Shared.activity!!.packageManager | |||
| .hasSystemFeature(feature) | |||
| } | |||
| private fun barCodeViewNotSet(result: MethodChannel.Result) { | |||
| result.error("404", "No barcode view found", null) | |||
| } | |||
| override fun getView(): View { | |||
| return initBarCodeView()?.apply { | |||
| if (!hasBackCamera()) { | |||
| if (!hasFrontCamera()) { | |||
| // No camera available! | |||
| } else { | |||
| this.cameraSettings.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT | |||
| } | |||
| } else { | |||
| this.cameraSettings.requestedCameraId = CameraInfo.CAMERA_FACING_BACK | |||
| } | |||
| resume() | |||
| }!! | |||
| } | |||
| private fun initBarCodeView(): BarcodeView? { | |||
| if (barcodeView == null) { | |||
| barcodeView = createBarCodeView() | |||
| barcodeView = BarcodeView(Shared.activity) | |||
| if (params["cameraFacing"] as Int == 1) { | |||
| barcodeView?.cameraSettings?.requestedCameraId = CameraInfo.CAMERA_FACING_FRONT | |||
| } | |||
| } | |||
| return barcodeView | |||
| } | |||
| private fun createBarCodeView(): BarcodeView { | |||
| val barcode = BarcodeView(Shared.activity) | |||
| barcode.decodeContinuous( | |||
| private fun startScan(arguments: List<Int>?, result: MethodChannel.Result) { | |||
| val allowedBarcodeTypes = mutableListOf<BarcodeFormat>() | |||
| try { | |||
| arguments?.forEach { | |||
| allowedBarcodeTypes.add(BarcodeFormat.values()[it]) | |||
| } | |||
| } catch (e: java.lang.Exception) { | |||
| result.error(null, null, null) | |||
| } | |||
| barcodeView?.decodeContinuous( | |||
| object : BarcodeCallback { | |||
| override fun barcodeResult(result: BarcodeResult) { | |||
| val code = mapOf( | |||
| "code" to result.text, | |||
| "type" to result.barcodeFormat.name, | |||
| "rawBytes" to result.rawBytes) | |||
| channel.invokeMethod("onRecognizeQR", code) | |||
| if (allowedBarcodeTypes.size == 0 || allowedBarcodeTypes.contains(result.barcodeFormat)) { | |||
| val code = mapOf( | |||
| "code" to result.text, | |||
| "type" to result.barcodeFormat.name, | |||
| "rawBytes" to result.rawBytes) | |||
| channel.invokeMethod("onRecognizeQR", code) | |||
| } | |||
| } | |||
| override fun possibleResultPoints(resultPoints: List<ResultPoint>) {} | |||
| } | |||
| ) | |||
| return barcode | |||
| } | |||
| private fun stopScan() { | |||
| barcodeView?.stopDecoding() | |||
| } | |||
| private fun getSystemFeatures(result: MethodChannel.Result) { | |||
| try { | |||
| result.success(mapOf("hasFrontCamera" to hasFrontCamera(), | |||
| "hasBackCamera" to hasBackCamera(), "hasFlash" to hasFlash(), | |||
| "activeCamera" to barcodeView?.cameraSettings?.requestedCameraId)) | |||
| } catch (e: Exception) { | |||
| result.error(null, null, null) | |||
| } | |||
| } | |||
| private fun hasCameraPermission(): Boolean { | |||
| @@ -156,29 +239,16 @@ class QRView(messenger: BinaryMessenger, id: Int, private val context: Context) | |||
| } | |||
| private fun checkAndRequestPermission(result: MethodChannel.Result?) { | |||
| if (Shared.cameraPermissionContinuation != null) { | |||
| result?.error("cameraPermission", "Camera permission request ongoing", null) | |||
| } | |||
| Shared.cameraPermissionContinuation = Runnable { | |||
| Shared.cameraPermissionContinuation = null | |||
| if (!hasCameraPermission()) { | |||
| result?.error( | |||
| "cameraPermission", "MediaRecorderCamera permission not granted", null) | |||
| return@Runnable | |||
| } | |||
| } | |||
| requestingPermission = false | |||
| if (hasCameraPermission()) { | |||
| Shared.cameraPermissionContinuation?.run() | |||
| } else { | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |||
| requestingPermission = true | |||
| when { | |||
| hasCameraPermission() -> result?.success(true) | |||
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { | |||
| Shared.activity?.requestPermissions( | |||
| arrayOf(Manifest.permission.CAMERA), | |||
| Shared.CAMERA_REQUEST_ID) | |||
| } | |||
| else -> { | |||
| result?.error("cameraPermission", "Platform Version to low for camera permission check", null) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -10,8 +10,9 @@ import io.flutter.plugin.platform.PlatformViewFactory | |||
| class QRViewFactory(private val messenger: BinaryMessenger) : | |||
| PlatformViewFactory(StandardMessageCodec.INSTANCE) { | |||
| override fun create(context: Context, id: Int, obj: Any?): PlatformView { | |||
| return QRView(messenger, id, context) | |||
| override fun create(context: Context, id: Int, args: Any?): PlatformView { | |||
| val params = args as HashMap<String, Any> | |||
| return QRView(messenger, id, params) | |||
| } | |||
| } | |||
| @@ -5,5 +5,4 @@ import android.app.Activity | |||
| object Shared { | |||
| const val CAMERA_REQUEST_ID = 513469796 | |||
| var activity: Activity? = null | |||
| var cameraPermissionContinuation: Runnable? = null | |||
| } | |||
| @@ -9,6 +9,7 @@ | |||
| .buildlog/ | |||
| .history | |||
| .svn/ | |||
| .last_build_id | |||
| # IntelliJ related | |||
| *.iml | |||
| @@ -9,11 +9,11 @@ | |||
| /* Begin PBXBuildFile section */ | |||
| 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; | |||
| 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; | |||
| 4FA3805501F593094264678B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75B64521967C76C845268E71 /* Pods_Runner.framework */; }; | |||
| 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; | |||
| 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; | |||
| 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; | |||
| 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; | |||
| F50BD2AA150CE43D961DBB69 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C211BF4383BB3311CB0313D6 /* Pods_Runner.framework */; }; | |||
| /* End PBXBuildFile section */ | |||
| /* Begin PBXCopyFilesBuildPhase section */ | |||
| @@ -30,15 +30,14 @@ | |||
| /* End PBXCopyFilesBuildPhase section */ | |||
| /* Begin PBXFileReference section */ | |||
| 11A772D5271EABFB7473B52C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | |||
| 08F42BD28C1585A831B4FDA2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; | |||
| 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; | |||
| 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; | |||
| 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; | |||
| 4D86354DC939411D3F7E41EA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; | |||
| 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; | |||
| 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | |||
| 75B64521967C76C845268E71 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | |||
| 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; | |||
| 96DCE88542EFC8C39614159F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; | |||
| 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; | |||
| 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; | |||
| 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; | |||
| @@ -46,7 +45,8 @@ | |||
| 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; | |||
| 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; | |||
| 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | |||
| C211BF4383BB3311CB0313D6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; | |||
| DB8991B7B046D94B2E68CEAD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; | |||
| EF8A0739582463E296AD6CB1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; | |||
| /* End PBXFileReference section */ | |||
| /* Begin PBXFrameworksBuildPhase section */ | |||
| @@ -54,21 +54,13 @@ | |||
| isa = PBXFrameworksBuildPhase; | |||
| buildActionMask = 2147483647; | |||
| files = ( | |||
| F50BD2AA150CE43D961DBB69 /* Pods_Runner.framework in Frameworks */, | |||
| 4FA3805501F593094264678B /* Pods_Runner.framework in Frameworks */, | |||
| ); | |||
| runOnlyForDeploymentPostprocessing = 0; | |||
| }; | |||
| /* End PBXFrameworksBuildPhase section */ | |||
| /* Begin PBXGroup section */ | |||
| 509AC3E235E568B6DDAAEC7D /* Frameworks */ = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| C211BF4383BB3311CB0313D6 /* Pods_Runner.framework */, | |||
| ); | |||
| name = Frameworks; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| 9740EEB11CF90186004384FC /* Flutter */ = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| @@ -87,7 +79,7 @@ | |||
| 97C146F01CF9000F007C117D /* Runner */, | |||
| 97C146EF1CF9000F007C117D /* Products */, | |||
| D7C58E57CAF69D9A14CC212F /* Pods */, | |||
| 509AC3E235E568B6DDAAEC7D /* Frameworks */, | |||
| AF031A634C9742F16BF4F89A /* Frameworks */, | |||
| ); | |||
| sourceTree = "<group>"; | |||
| }; | |||
| @@ -122,12 +114,20 @@ | |||
| name = "Supporting Files"; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| AF031A634C9742F16BF4F89A /* Frameworks */ = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| 75B64521967C76C845268E71 /* Pods_Runner.framework */, | |||
| ); | |||
| name = Frameworks; | |||
| sourceTree = "<group>"; | |||
| }; | |||
| D7C58E57CAF69D9A14CC212F /* Pods */ = { | |||
| isa = PBXGroup; | |||
| children = ( | |||
| 4D86354DC939411D3F7E41EA /* Pods-Runner.debug.xcconfig */, | |||
| 11A772D5271EABFB7473B52C /* Pods-Runner.release.xcconfig */, | |||
| 96DCE88542EFC8C39614159F /* Pods-Runner.profile.xcconfig */, | |||
| DB8991B7B046D94B2E68CEAD /* Pods-Runner.debug.xcconfig */, | |||
| EF8A0739582463E296AD6CB1 /* Pods-Runner.release.xcconfig */, | |||
| 08F42BD28C1585A831B4FDA2 /* Pods-Runner.profile.xcconfig */, | |||
| ); | |||
| path = Pods; | |||
| sourceTree = "<group>"; | |||
| @@ -139,14 +139,14 @@ | |||
| isa = PBXNativeTarget; | |||
| buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; | |||
| buildPhases = ( | |||
| 854A11C1BE2493D5A6BD85DB /* [CP] Check Pods Manifest.lock */, | |||
| 7BEA526A4A0DFAAC4945EC09 /* [CP] Check Pods Manifest.lock */, | |||
| 9740EEB61CF901F6004384FC /* Run Script */, | |||
| 97C146EA1CF9000F007C117D /* Sources */, | |||
| 97C146EB1CF9000F007C117D /* Frameworks */, | |||
| 97C146EC1CF9000F007C117D /* Resources */, | |||
| 9705A1C41CF9048500538489 /* Embed Frameworks */, | |||
| 3B06AD1E1E4923F5004D2608 /* Thin Binary */, | |||
| FDE6A3C9913AC5840FF42544 /* [CP] Embed Pods Frameworks */, | |||
| 8DB6578C615972DBF2DFAA0D /* [CP] Embed Pods Frameworks */, | |||
| ); | |||
| buildRules = ( | |||
| ); | |||
| @@ -168,7 +168,7 @@ | |||
| TargetAttributes = { | |||
| 97C146ED1CF9000F007C117D = { | |||
| CreatedOnToolsVersion = 7.3.1; | |||
| DevelopmentTeam = 5LSCTACHT8; | |||
| DevelopmentTeam = RCH2VG82SH; | |||
| LastSwiftMigration = 1020; | |||
| ProvisioningStyle = Automatic; | |||
| }; | |||
| @@ -221,7 +221,7 @@ | |||
| shellPath = /bin/sh; | |||
| shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; | |||
| }; | |||
| 854A11C1BE2493D5A6BD85DB /* [CP] Check Pods Manifest.lock */ = { | |||
| 7BEA526A4A0DFAAC4945EC09 /* [CP] Check Pods Manifest.lock */ = { | |||
| isa = PBXShellScriptBuildPhase; | |||
| buildActionMask = 2147483647; | |||
| files = ( | |||
| @@ -243,41 +243,41 @@ | |||
| shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; | |||
| showEnvVarsInLog = 0; | |||
| }; | |||
| 9740EEB61CF901F6004384FC /* Run Script */ = { | |||
| 8DB6578C615972DBF2DFAA0D /* [CP] Embed Pods Frameworks */ = { | |||
| isa = PBXShellScriptBuildPhase; | |||
| buildActionMask = 2147483647; | |||
| files = ( | |||
| ); | |||
| inputPaths = ( | |||
| "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | |||
| "${PODS_ROOT}/../Flutter/Flutter.framework", | |||
| "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", | |||
| "${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework", | |||
| ); | |||
| name = "Run Script"; | |||
| name = "[CP] Embed Pods Frameworks"; | |||
| outputPaths = ( | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework", | |||
| ); | |||
| runOnlyForDeploymentPostprocessing = 0; | |||
| shellPath = /bin/sh; | |||
| shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | |||
| shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | |||
| showEnvVarsInLog = 0; | |||
| }; | |||
| FDE6A3C9913AC5840FF42544 /* [CP] Embed Pods Frameworks */ = { | |||
| 9740EEB61CF901F6004384FC /* Run Script */ = { | |||
| isa = PBXShellScriptBuildPhase; | |||
| buildActionMask = 2147483647; | |||
| files = ( | |||
| ); | |||
| inputPaths = ( | |||
| "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", | |||
| "${PODS_ROOT}/../Flutter/Flutter.framework", | |||
| "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", | |||
| "${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework", | |||
| ); | |||
| name = "[CP] Embed Pods Frameworks"; | |||
| name = "Run Script"; | |||
| outputPaths = ( | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", | |||
| "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework", | |||
| ); | |||
| runOnlyForDeploymentPostprocessing = 0; | |||
| shellPath = /bin/sh; | |||
| shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; | |||
| showEnvVarsInLog = 0; | |||
| shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; | |||
| }; | |||
| /* End PBXShellScriptBuildPhase section */ | |||
| @@ -372,7 +372,7 @@ | |||
| CODE_SIGN_IDENTITY = "iPhone Developer"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = 5LSCTACHT8; | |||
| DEVELOPMENT_TEAM = RCH2VG82SH; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| "$(inherited)", | |||
| @@ -510,7 +510,7 @@ | |||
| CODE_SIGN_IDENTITY = "iPhone Developer"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = 5LSCTACHT8; | |||
| DEVELOPMENT_TEAM = RCH2VG82SH; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| "$(inherited)", | |||
| @@ -541,7 +541,7 @@ | |||
| CODE_SIGN_IDENTITY = "iPhone Developer"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = 5LSCTACHT8; | |||
| DEVELOPMENT_TEAM = RCH2VG82SH; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| "$(inherited)", | |||
| @@ -6,11 +6,6 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; | |||
| void main() => runApp(MaterialApp(home: QRViewExample())); | |||
| const flashOn = 'FLASH ON'; | |||
| const flashOff = 'FLASH OFF'; | |||
| const frontCamera = 'FRONT CAMERA'; | |||
| const backCamera = 'BACK CAMERA'; | |||
| class QRViewExample extends StatefulWidget { | |||
| const QRViewExample({ | |||
| Key key, | |||
| @@ -22,8 +17,6 @@ class QRViewExample extends StatefulWidget { | |||
| class _QRViewExampleState extends State<QRViewExample> { | |||
| Barcode result; | |||
| var flashState = flashOn; | |||
| var cameraState = frontCamera; | |||
| QRViewController controller; | |||
| final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); | |||
| @@ -64,44 +57,33 @@ class _QRViewExampleState extends State<QRViewExample> { | |||
| Container( | |||
| margin: EdgeInsets.all(8), | |||
| child: RaisedButton( | |||
| onPressed: () { | |||
| if (controller != null) { | |||
| controller.toggleFlash(); | |||
| if (_isFlashOn(flashState)) { | |||
| setState(() { | |||
| flashState = flashOff; | |||
| }); | |||
| } else { | |||
| setState(() { | |||
| flashState = flashOn; | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| child: | |||
| Text(flashState, style: TextStyle(fontSize: 20)), | |||
| ), | |||
| onPressed: () => setState(() { | |||
| controller?.toggleFlash(); | |||
| }), | |||
| child: FutureBuilder( | |||
| future: controller?.getFlashStatus(), | |||
| builder: (context, snapshot) { | |||
| return Text('Flash: ${snapshot.data}'); | |||
| }, | |||
| )), | |||
| ), | |||
| Container( | |||
| margin: EdgeInsets.all(8), | |||
| child: RaisedButton( | |||
| onPressed: () { | |||
| if (controller != null) { | |||
| controller.flipCamera(); | |||
| if (_isBackCamera(cameraState)) { | |||
| setState(() { | |||
| cameraState = frontCamera; | |||
| }); | |||
| } else { | |||
| setState(() { | |||
| cameraState = backCamera; | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| child: | |||
| Text(cameraState, style: TextStyle(fontSize: 20)), | |||
| ), | |||
| onPressed: () => setState(() { | |||
| controller?.flipCamera(); | |||
| }), | |||
| child: FutureBuilder( | |||
| future: controller?.getCameraInfo(), | |||
| builder: (context, snapshot) { | |||
| if (snapshot.data != null) { | |||
| return Text( | |||
| 'Camera facing ${describeEnum(snapshot.data)}'); | |||
| } else { | |||
| return Text('loading'); | |||
| } | |||
| }, | |||
| )), | |||
| ) | |||
| ], | |||
| ), | |||
| @@ -138,14 +120,6 @@ class _QRViewExampleState extends State<QRViewExample> { | |||
| ); | |||
| } | |||
| bool _isFlashOn(String current) { | |||
| return flashOn == current; | |||
| } | |||
| bool _isBackCamera(String current) { | |||
| return backCamera == current; | |||
| } | |||
| Widget _buildQrView(BuildContext context) { | |||
| // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. | |||
| var scanArea = (MediaQuery.of(context).size.width < 400 || | |||
| @@ -154,29 +128,25 @@ class _QRViewExampleState extends State<QRViewExample> { | |||
| : 300.0; | |||
| // To ensure the Scanner view is properly sizes after rotation | |||
| // we need to listen for Flutter SizeChanged notification and update controller | |||
| return NotificationListener<SizeChangedLayoutNotification>( | |||
| onNotification: (notification) { | |||
| Future.microtask( | |||
| () => controller?.updateDimensions(qrKey, scanArea: scanArea)); | |||
| return false; | |||
| }, | |||
| child: SizeChangedLayoutNotifier( | |||
| key: const Key('qr-size-notifier'), | |||
| child: QRView( | |||
| key: qrKey, | |||
| onQRViewCreated: _onQRViewCreated, | |||
| overlay: QrScannerOverlayShape( | |||
| borderColor: Colors.red, | |||
| borderRadius: 10, | |||
| borderLength: 30, | |||
| borderWidth: 10, | |||
| cutOutSize: scanArea, | |||
| ), | |||
| ))); | |||
| return QRView( | |||
| key: qrKey, | |||
| cameraFacing: CameraFacing.front, | |||
| onQRViewCreated: _onQRViewCreated, | |||
| formatsAllowed: [BarcodeFormat.qrcode], | |||
| overlay: QrScannerOverlayShape( | |||
| borderColor: Colors.red, | |||
| borderRadius: 10, | |||
| borderLength: 30, | |||
| borderWidth: 10, | |||
| cutOutSize: scanArea, | |||
| ), | |||
| ); | |||
| } | |||
| void _onQRViewCreated(QRViewController controller) { | |||
| this.controller = controller; | |||
| setState(() { | |||
| this.controller = controller; | |||
| }); | |||
| controller.scannedDataStream.listen((scanData) { | |||
| setState(() { | |||
| result = scanData; | |||
| @@ -3,7 +3,7 @@ description: Demonstrates how to use the flutter_qr plugin. | |||
| publish_to: 'none' | |||
| environment: | |||
| sdk: ">=2.3.0 <3.0.0" | |||
| sdk: ">=2.6.0 <3.0.0" | |||
| dependencies: | |||
| flutter: | |||
| @@ -13,75 +13,56 @@ public class QRView:NSObject,FlutterPlatformView { | |||
| var scanner: MTBBarcodeScanner? | |||
| var registrar: FlutterPluginRegistrar | |||
| var channel: FlutterMethodChannel | |||
| var cameraFacing: MTBCamera | |||
| public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64){ | |||
| var QRCodeTypes = [ | |||
| 0: AVMetadataObject.ObjectType.aztec, | |||
| 2: AVMetadataObject.ObjectType.code39, | |||
| 3: AVMetadataObject.ObjectType.code93, | |||
| 4: AVMetadataObject.ObjectType.code128, | |||
| 5: AVMetadataObject.ObjectType.dataMatrix, | |||
| 6: AVMetadataObject.ObjectType.ean8, | |||
| 7: AVMetadataObject.ObjectType.ean13, | |||
| 8: AVMetadataObject.ObjectType.interleaved2of5, | |||
| 10: AVMetadataObject.ObjectType.pdf417, | |||
| 11: AVMetadataObject.ObjectType.qr, | |||
| 15: AVMetadataObject.ObjectType.upce | |||
| ] | |||
| public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary<String, Any>){ | |||
| self.registrar = registrar | |||
| previewView = UIView(frame: frame) | |||
| cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back | |||
| channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger()) | |||
| } | |||
| func isCameraAvailable(success: Bool) -> Void { | |||
| if success { | |||
| do { | |||
| try scanner?.startScanning(resultBlock: { [weak self] codes in | |||
| if let codes = codes { | |||
| for code in codes { | |||
| var typeString: String; | |||
| switch(code.type) { | |||
| case AVMetadataObject.ObjectType.aztec: | |||
| typeString = "AZTEC" | |||
| case AVMetadataObject.ObjectType.code39: | |||
| typeString = "CODE_39" | |||
| case AVMetadataObject.ObjectType.code93: | |||
| typeString = "CODE_93" | |||
| case AVMetadataObject.ObjectType.code128: | |||
| typeString = "CODE_128" | |||
| case AVMetadataObject.ObjectType.dataMatrix: | |||
| typeString = "DATA_MATRIX" | |||
| case AVMetadataObject.ObjectType.ean8: | |||
| typeString = "EAN_8" | |||
| case AVMetadataObject.ObjectType.ean13: | |||
| typeString = "EAN_13" | |||
| case AVMetadataObject.ObjectType.itf14: | |||
| typeString = "ITF" | |||
| case AVMetadataObject.ObjectType.pdf417: | |||
| typeString = "PDF_417" | |||
| case AVMetadataObject.ObjectType.qr: | |||
| typeString = "QR_CODE" | |||
| case AVMetadataObject.ObjectType.upce: | |||
| typeString = "UPC_E" | |||
| default: | |||
| return | |||
| } | |||
| guard let stringValue = code.stringValue else { continue } | |||
| let result = ["code": stringValue, "type": typeString] | |||
| self?.channel.invokeMethod("onRecognizeQR", arguments: result) | |||
| } | |||
| } | |||
| }) | |||
| } catch { | |||
| NSLog("Unable to start scanning") | |||
| } | |||
| } else { | |||
| UIAlertView(title: "Scanning Unavailable", message: "This app does not have permission to access the camera", delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "Ok").show() | |||
| } | |||
| deinit { | |||
| scanner?.stopScanning() | |||
| } | |||
| public func view() -> UIView { | |||
| channel.setMethodCallHandler({ | |||
| [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in | |||
| [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in | |||
| switch(call.method){ | |||
| case "setDimensions": | |||
| let arguments = call.arguments as! Dictionary<String, Double> | |||
| self?.setDimensions(width: arguments["width"] ?? 0, height: arguments["height"] ?? 0, scanArea: arguments["scanArea"] ?? 0) | |||
| case "startScan": | |||
| self?.startScan(call.arguments as! Array<Int>, result) | |||
| case "flipCamera": | |||
| self?.flipCamera() | |||
| self?.flipCamera(result) | |||
| case "toggleFlash": | |||
| self?.toggleFlash() | |||
| self?.toggleFlash(result) | |||
| case "pauseCamera": | |||
| self?.pauseCamera() | |||
| self?.pauseCamera(result) | |||
| case "resumeCamera": | |||
| self?.resumeCamera() | |||
| self?.resumeCamera(result) | |||
| case "getCameraInfo": | |||
| self?.getCameraInfo(result) | |||
| case "getFlashInfo": | |||
| self?.getFlashInfo(result) | |||
| case "getSystemFeatures": | |||
| self?.getSystemFeatures(result) | |||
| default: | |||
| result(FlutterMethodNotImplemented) | |||
| return | |||
| @@ -106,41 +87,162 @@ public class QRView:NSObject,FlutterPlatformView { | |||
| self.scanner?.scanRect = CGRect(x: Double(midX) - (scanArea / 2), y: Double(midY) - (scanArea / 2), width: scanArea, height: scanArea) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func startScan(_ arguments: Array<Int>, _ result: @escaping FlutterResult) -> Void { | |||
| scanner = MTBBarcodeScanner(previewView: previewView) | |||
| var allowedBarcodeTypes: Array<AVMetadataObject.ObjectType> = [] | |||
| arguments.forEach { arg in | |||
| allowedBarcodeTypes.append( QRCodeTypes[arg]!) | |||
| } | |||
| MTBBarcodeScanner.requestCameraPermission(success: isCameraAvailable) | |||
| MTBBarcodeScanner.requestCameraPermission(success: { permissionGranted in | |||
| if permissionGranted { | |||
| do { | |||
| try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in | |||
| if let codes = codes { | |||
| for code in codes { | |||
| var typeString: String; | |||
| switch(code.type) { | |||
| case AVMetadataObject.ObjectType.aztec: | |||
| typeString = "AZTEC" | |||
| case AVMetadataObject.ObjectType.code39: | |||
| typeString = "CODE_39" | |||
| case AVMetadataObject.ObjectType.code93: | |||
| typeString = "CODE_93" | |||
| case AVMetadataObject.ObjectType.code128: | |||
| typeString = "CODE_128" | |||
| case AVMetadataObject.ObjectType.dataMatrix: | |||
| typeString = "DATA_MATRIX" | |||
| case AVMetadataObject.ObjectType.ean8: | |||
| typeString = "EAN_8" | |||
| case AVMetadataObject.ObjectType.ean13: | |||
| typeString = "EAN_13" | |||
| case AVMetadataObject.ObjectType.itf14: | |||
| typeString = "ITF" | |||
| case AVMetadataObject.ObjectType.pdf417: | |||
| typeString = "PDF_417" | |||
| case AVMetadataObject.ObjectType.qr: | |||
| typeString = "QR_CODE" | |||
| case AVMetadataObject.ObjectType.upce: | |||
| typeString = "UPC_E" | |||
| default: | |||
| return | |||
| } | |||
| guard let stringValue = code.stringValue else { continue } | |||
| let result = ["code": stringValue, "type": typeString] | |||
| if allowedBarcodeTypes.count == 0 || allowedBarcodeTypes.contains(code.type) { | |||
| self?.channel.invokeMethod("onRecognizeQR", arguments: result) | |||
| } | |||
| } | |||
| } | |||
| }) | |||
| } catch { | |||
| let error = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: nil) | |||
| result(error) | |||
| } | |||
| } else { | |||
| let error = FlutterError(code: "cameraPermission", message: "Permission denied to access the camera", details: nil) | |||
| result(error) | |||
| } | |||
| }) | |||
| } | |||
| func stopScan(){ | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if sc.isScanning() { | |||
| sc.stopScanning() | |||
| } | |||
| } | |||
| } | |||
| func getCameraInfo(_ result: @escaping FlutterResult) -> Void { | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| result(sc.camera.rawValue) | |||
| } else { | |||
| let error = FlutterError(code: "cameraInformationError", message: "Could not get camera information", details: nil) | |||
| result(error) | |||
| } | |||
| } | |||
| func flipCamera(){ | |||
| func flipCamera(_ result: @escaping FlutterResult){ | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if sc.hasOppositeCamera() { | |||
| sc.flipCamera() | |||
| } | |||
| return result(sc.camera.rawValue) | |||
| } | |||
| return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) | |||
| } | |||
| func getFlashInfo(_ result: @escaping FlutterResult) -> Void { | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| result(sc.torchMode.rawValue != 0) | |||
| } else { | |||
| let error = FlutterError(code: "cameraInformationError", message: "Could not get flash information", details: nil) | |||
| result(error) | |||
| } | |||
| } | |||
| func toggleFlash(){ | |||
| func toggleFlash(_ result: @escaping FlutterResult){ | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if sc.hasTorch() { | |||
| sc.toggleTorch() | |||
| return result(sc.torchMode == MTBTorchMode(rawValue: 1)) | |||
| } | |||
| return result(FlutterError(code: "404", message: "This device doesn\'t support flash", details: nil)) | |||
| } | |||
| return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) | |||
| } | |||
| func pauseCamera() { | |||
| func pauseCamera(_ result: @escaping FlutterResult) { | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if sc.isScanning() { | |||
| sc.freezeCapture() | |||
| } | |||
| return result(true) | |||
| } | |||
| return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) | |||
| } | |||
| func resumeCamera() { | |||
| func resumeCamera(_ result: @escaping FlutterResult) { | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| if !sc.isScanning() { | |||
| sc.unfreezeCapture() | |||
| } | |||
| return result(true) | |||
| } | |||
| return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) | |||
| } | |||
| func getSystemFeatures(_ result: @escaping FlutterResult) -> Void { | |||
| if let sc: MTBBarcodeScanner = scanner { | |||
| var hasBackCameraVar = false | |||
| var hasFrontCameraVar = false | |||
| let camera = sc.camera | |||
| if(camera == MTBCamera(rawValue: 0)){ | |||
| hasBackCameraVar = true | |||
| if sc.hasOppositeCamera() { | |||
| hasFrontCameraVar = true | |||
| } | |||
| }else{ | |||
| hasFrontCameraVar = true | |||
| if sc.hasOppositeCamera() { | |||
| hasBackCameraVar = true | |||
| } | |||
| } | |||
| return result([ | |||
| "hasFrontCamera": hasFrontCameraVar, | |||
| "hasBackCamera": hasBackCameraVar, | |||
| "hasFlash": sc.hasTorch(), | |||
| "activeCamera": camera.rawValue | |||
| ]) | |||
| } | |||
| return result(FlutterError(code: "404", message: nil, details: nil)) | |||
| } | |||
| } | |||
| } | |||
| @@ -17,8 +17,8 @@ public class QRViewFactory: NSObject, FlutterPlatformViewFactory { | |||
| } | |||
| public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { | |||
| let dictionary = args as! Dictionary<String, Double> | |||
| return QRView(withFrame: CGRect(x: 0, y: 0, width: dictionary["width"] ?? 0, height: dictionary["height"] ?? 0), withRegistrar: registrar!,withId: viewId) | |||
| let params = args as! Dictionary<String, Double> | |||
| return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params) | |||
| } | |||
| public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { | |||
| @@ -1,2 +1,7 @@ | |||
| export 'src/qr_code_scanner.dart'; | |||
| export 'src/qr_scanner_overlay_shape.dart'; | |||
| export 'src/types/barcode.dart'; | |||
| export 'src/types/barcode_format.dart'; | |||
| export 'src/types/camera.dart'; | |||
| export 'src/types/camera_exception.dart'; | |||
| export 'src/types/features.dart'; | |||
| @@ -3,138 +3,107 @@ import 'dart:async'; | |||
| import 'package:flutter/foundation.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter/services.dart'; | |||
| import 'package:qr_code_scanner/qr_code_scanner.dart'; | |||
| typedef QRViewCreatedCallback = void Function(QRViewController); | |||
| enum BarcodeFormat { | |||
| /// Aztec 2D barcode format. | |||
| aztec, | |||
| /// CODABAR 1D format. | |||
| codabar, | |||
| /// Code 39 1D format. | |||
| code39, | |||
| /// Code 93 1D format. | |||
| code93, | |||
| /// Code 128 1D format. | |||
| code128, | |||
| /// Data Matrix 2D barcode format. | |||
| dataMatrix, | |||
| /// EAN-8 1D format. | |||
| ean8, | |||
| /// EAN-13 1D format. | |||
| ean13, | |||
| /// ITF (Interleaved Two of Five) 1D format. | |||
| itf, | |||
| /// MaxiCode 2D barcode format. | |||
| maxicode, | |||
| /// PDF417 format. | |||
| pdf417, | |||
| /// QR Code 2D barcode format. | |||
| qrcode, | |||
| /// RSS 14 | |||
| rss14, | |||
| /// RSS EXPANDED | |||
| rssExpanded, | |||
| /// UPC-A 1D format. | |||
| upcA, | |||
| import 'qr_scanner_overlay_shape.dart'; | |||
| import 'types/barcode.dart'; | |||
| import 'types/barcode_format.dart'; | |||
| import 'types/camera.dart'; | |||
| import 'types/camera_exception.dart'; | |||
| import 'types/features.dart'; | |||
| /// UPC-E 1D format. | |||
| upcE, | |||
| /// UPC/EAN extension format. Not a stand-alone format. | |||
| upcEanExtension | |||
| } | |||
| const _formatNames = <String, BarcodeFormat>{ | |||
| 'AZTEC': BarcodeFormat.aztec, | |||
| 'CODABAR': BarcodeFormat.codabar, | |||
| 'CODE_39': BarcodeFormat.code39, | |||
| 'CODE_93': BarcodeFormat.code93, | |||
| 'CODE_128': BarcodeFormat.code128, | |||
| 'DATA_MATRIX': BarcodeFormat.dataMatrix, | |||
| 'EAN_8': BarcodeFormat.ean8, | |||
| 'EAN_13': BarcodeFormat.ean13, | |||
| 'ITF': BarcodeFormat.itf, | |||
| 'MAXICODE': BarcodeFormat.maxicode, | |||
| 'PDF_417': BarcodeFormat.pdf417, | |||
| 'QR_CODE': BarcodeFormat.qrcode, | |||
| 'RSS_14': BarcodeFormat.rss14, | |||
| 'RSS_EXPANDED': BarcodeFormat.rssExpanded, | |||
| 'UPC_A': BarcodeFormat.upcA, | |||
| 'UPC_E': BarcodeFormat.upcE, | |||
| 'UPC_EAN_EXTENSION': BarcodeFormat.upcEanExtension, | |||
| }; | |||
| class Barcode { | |||
| Barcode(this.code, this.format, this.rawBytes); | |||
| final String code; | |||
| final BarcodeFormat format; | |||
| /// Raw bytes are only supported by Android. | |||
| final List<int> rawBytes; | |||
| } | |||
| typedef QRViewCreatedCallback = void Function(QRViewController); | |||
| typedef PermissionSetCallback = void Function(QRViewController, bool); | |||
| /// The [QRView] is the view where the camera | |||
| /// and the barcode scanner gets displayed. | |||
| class QRView extends StatefulWidget { | |||
| const QRView({ | |||
| @required Key key, | |||
| @required this.onQRViewCreated, | |||
| this.overlay, | |||
| this.overlayMargin = EdgeInsets.zero, | |||
| this.cameraFacing = CameraFacing.back, | |||
| this.onPermissionSet, | |||
| this.formatsAllowed, | |||
| }) : assert(key != null), | |||
| assert(onQRViewCreated != null), | |||
| super(key: key); | |||
| /// [onQRViewCreated] gets called when the view is created | |||
| final QRViewCreatedCallback onQRViewCreated; | |||
| /// Use [overlay] to provide an overlay for the view. | |||
| /// This can be used to create a certain scan area. | |||
| final ShapeBorder overlay; | |||
| /// Use [overlayMargin] to provide a margin to [overlay] | |||
| final EdgeInsetsGeometry overlayMargin; | |||
| /// Set which camera to use on startup. | |||
| /// | |||
| /// [cameraFacing] can either be CameraFacing.front or CameraFacing.back. | |||
| /// Defaults to CameraFacing.back | |||
| final CameraFacing cameraFacing; | |||
| /// Calls the provided [onPermissionSet] callback when the permission is set. | |||
| final PermissionSetCallback onPermissionSet; | |||
| /// Use [formatsAllowed] to specify which formats needs to be scanned. | |||
| final List<BarcodeFormat> formatsAllowed; | |||
| @override | |||
| State<StatefulWidget> createState() => _QRViewState(); | |||
| } | |||
| class _QRViewState extends State<QRView> { | |||
| var _channel; | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return NotificationListener( | |||
| onNotification: onNotification, | |||
| child: SizeChangedLayoutNotifier( | |||
| child: (widget.overlay != null) | |||
| ? _getPlatformQrViewWithOverlay() | |||
| : _getPlatformQrView(), | |||
| ), | |||
| ); | |||
| } | |||
| bool onNotification(notification) { | |||
| Future.microtask(() => { | |||
| QRViewController.updateDimensions(widget.key, _channel, | |||
| scanArea: widget.overlay != null | |||
| ? (widget.overlay as QrScannerOverlayShape).cutOutSize | |||
| : 0.0) | |||
| }); | |||
| return false; | |||
| } | |||
| Widget _getPlatformQrViewWithOverlay() { | |||
| return Stack( | |||
| children: [ | |||
| _getPlatformQrView(widget.key), | |||
| if (widget.overlay != null) | |||
| Container( | |||
| padding: widget.overlayMargin, | |||
| decoration: ShapeDecoration( | |||
| shape: widget.overlay, | |||
| ), | |||
| ) | |||
| else | |||
| Container(), | |||
| _getPlatformQrView(), | |||
| Container( | |||
| padding: widget.overlayMargin, | |||
| decoration: ShapeDecoration( | |||
| shape: widget.overlay, | |||
| ), | |||
| ) | |||
| ], | |||
| ); | |||
| } | |||
| Widget _getPlatformQrView(GlobalKey key) { | |||
| Widget _getPlatformQrView() { | |||
| Widget _platformQrView; | |||
| switch (defaultTargetPlatform) { | |||
| case TargetPlatform.android: | |||
| _platformQrView = AndroidView( | |||
| viewType: 'net.touchcapture.qr.flutterqr/qrview', | |||
| onPlatformViewCreated: _onPlatformViewCreated, | |||
| creationParams: | |||
| _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), | |||
| creationParamsCodec: StandardMessageCodec(), | |||
| ); | |||
| break; | |||
| case TargetPlatform.iOS: | |||
| @@ -142,8 +111,7 @@ class _QRViewState extends State<QRView> { | |||
| viewType: 'net.touchcapture.qr.flutterqr/qrview', | |||
| onPlatformViewCreated: _onPlatformViewCreated, | |||
| creationParams: | |||
| _CreationParams.fromWidget(MediaQuery.of(context).size.width, 400) | |||
| .toMap(), | |||
| _QrCameraSettings(cameraFacing: widget.cameraFacing).toMap(), | |||
| creationParamsCodec: StandardMessageCodec(), | |||
| ); | |||
| break; | |||
| @@ -155,101 +123,178 @@ class _QRViewState extends State<QRView> { | |||
| } | |||
| void _onPlatformViewCreated(int id) { | |||
| if (widget.onQRViewCreated == null) { | |||
| return; | |||
| } | |||
| // We pass the cutout size so that the scanner respects the scan area. | |||
| var cutOutSize = 0.0; | |||
| if (widget.overlay != null) { | |||
| cutOutSize = (widget.overlay as QrScannerOverlayShape).cutOutSize; | |||
| } | |||
| widget.onQRViewCreated(QRViewController._(id, widget.key, cutOutSize)); | |||
| } | |||
| } | |||
| _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id'); | |||
| class _CreationParams { | |||
| _CreationParams({this.width, this.height}); | |||
| // Start scan after creation of the view | |||
| final controller = QRViewController._( | |||
| _channel, widget.key, cutOutSize, widget.onPermissionSet) | |||
| .._startScan(widget.key, cutOutSize, widget.formatsAllowed); | |||
| static _CreationParams fromWidget(double width, double height) { | |||
| return _CreationParams( | |||
| width: width, | |||
| height: height, | |||
| ); | |||
| // Initialize the controller for controlling the QRView | |||
| if (widget.onQRViewCreated != null) { | |||
| widget.onQRViewCreated(controller); | |||
| } | |||
| } | |||
| } | |||
| final double width; | |||
| final double height; | |||
| class _QrCameraSettings { | |||
| _QrCameraSettings({ | |||
| this.cameraFacing, | |||
| }); | |||
| final CameraFacing cameraFacing; | |||
| Map<String, dynamic> toMap() { | |||
| return <String, dynamic>{ | |||
| 'width': width, | |||
| 'height': height, | |||
| 'cameraFacing': cameraFacing.index, | |||
| }; | |||
| } | |||
| } | |||
| class QRViewController { | |||
| QRViewController._(int id, GlobalKey qrKey, double scanArea) | |||
| : _channel = MethodChannel('net.touchcapture.qr.flutterqr/qrview_$id') { | |||
| updateDimensions(qrKey, scanArea: scanArea); | |||
| _channel.setMethodCallHandler( | |||
| (call) async { | |||
| switch (call.method) { | |||
| case scanMethodCall: | |||
| if (call.arguments != null) { | |||
| final args = call.arguments as Map; | |||
| final code = args['code'] as String; | |||
| final rawType = args['type'] as String; | |||
| // Raw bytes are only supported by Android. | |||
| final rawBytes = args['rawBytes'] as List<int>; | |||
| final format = _formatNames[rawType]; | |||
| if (format != null) { | |||
| final barcode = Barcode(code, format, rawBytes); | |||
| _scanUpdateController.sink.add(barcode); | |||
| } else { | |||
| throw Exception('Unexpected barcode type $rawType'); | |||
| } | |||
| QRViewController._(MethodChannel channel, GlobalKey qrKey, double scanArea, | |||
| PermissionSetCallback onPermissionSet) | |||
| : _channel = channel { | |||
| _channel.setMethodCallHandler((call) async { | |||
| switch (call.method) { | |||
| case 'onRecognizeQR': | |||
| if (call.arguments != null) { | |||
| final args = call.arguments as Map; | |||
| final code = args['code'] as String; | |||
| final rawType = args['type'] as String; | |||
| // Raw bytes are only supported by Android. | |||
| final rawBytes = args['rawBytes'] as List<int>; | |||
| final format = BarcodeTypesExtension.fromString(rawType); | |||
| if (format != null) { | |||
| final barcode = Barcode(code, format, rawBytes); | |||
| _scanUpdateController.sink.add(barcode); | |||
| } else { | |||
| throw Exception('Unexpected barcode type $rawType'); | |||
| } | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| break; | |||
| case 'onPermissionSet': | |||
| await getSystemFeatures(); // if we have no permission all features will not be avaible | |||
| if (call.arguments != null) { | |||
| if (call.arguments as bool) { | |||
| _hasPermissions = true; | |||
| } else { | |||
| _hasPermissions = false; | |||
| } | |||
| if (onPermissionSet != null) { | |||
| onPermissionSet(this, call.arguments as bool); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| }); | |||
| } | |||
| static const scanMethodCall = 'onRecognizeQR'; | |||
| final MethodChannel _channel; | |||
| final StreamController<Barcode> _scanUpdateController = | |||
| StreamController<Barcode>(); | |||
| Stream<Barcode> get scannedDataStream => _scanUpdateController.stream; | |||
| void flipCamera() { | |||
| _channel.invokeMethod('flipCamera'); | |||
| SystemFeatures _features; | |||
| bool _hasPermissions; | |||
| SystemFeatures get systemFeatures => _features; | |||
| bool get hasPermissions => _hasPermissions; | |||
| /// Starts the barcode scanner | |||
| Future<void> _startScan(GlobalKey key, double cutOutSize, | |||
| List<BarcodeFormat> barcodeFormats) async { | |||
| // We need to update the dimension before the scan is started. | |||
| QRViewController.updateDimensions(key, _channel, scanArea: cutOutSize); | |||
| return _channel.invokeMethod( | |||
| 'startScan', barcodeFormats?.map((e) => e.asInt())?.toList() ?? []); | |||
| } | |||
| void toggleFlash() { | |||
| _channel.invokeMethod('toggleFlash'); | |||
| /// Gets information about which camera is active. | |||
| Future<CameraFacing> getCameraInfo() async { | |||
| try { | |||
| return CameraFacing | |||
| .values[await _channel.invokeMethod('getCameraInfo') as int]; | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| /// Flips the camera between available modes | |||
| Future<CameraFacing> flipCamera() async { | |||
| try { | |||
| return CameraFacing | |||
| .values[await _channel.invokeMethod('flipCamera') as int]; | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| void pauseCamera() { | |||
| _channel.invokeMethod('pauseCamera'); | |||
| /// Get flashlight status | |||
| Future<bool> getFlashStatus() async { | |||
| try { | |||
| return await _channel.invokeMethod('getFlashInfo'); | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| /// Toggles the flashlight between available modes | |||
| Future<void> toggleFlash() async { | |||
| try { | |||
| await _channel.invokeMethod('toggleFlash') as bool; | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| void resumeCamera() { | |||
| _channel.invokeMethod('resumeCamera'); | |||
| /// Pauses barcode scanning | |||
| Future<void> pauseCamera() async { | |||
| try { | |||
| await _channel.invokeMethod('pauseCamera'); | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| /// Resumes barcode scanning | |||
| Future<void> resumeCamera() async { | |||
| try { | |||
| await _channel.invokeMethod('resumeCamera'); | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| /// Returns which features are available on device. | |||
| Future<SystemFeatures> getSystemFeatures() async { | |||
| try { | |||
| var features = | |||
| await _channel.invokeMapMethod<String, dynamic>('getSystemFeatures'); | |||
| return SystemFeatures.fromJson(features); | |||
| } on PlatformException catch (e) { | |||
| throw CameraException(e.code, e.message); | |||
| } | |||
| } | |||
| /// Disposes the barcode stream. | |||
| void dispose() { | |||
| _scanUpdateController.close(); | |||
| } | |||
| void updateDimensions(GlobalKey key, {double scanArea}) { | |||
| /// Updates the view dimensions for iOS. | |||
| static void updateDimensions(GlobalKey key, MethodChannel channel, | |||
| {double scanArea}) { | |||
| if (defaultTargetPlatform == TargetPlatform.iOS) { | |||
| final RenderBox renderBox = key.currentContext.findRenderObject(); | |||
| _channel.invokeMethod('setDimensions', { | |||
| channel.invokeMethod('setDimensions', { | |||
| 'width': renderBox.size.width, | |||
| 'height': renderBox.size.height, | |||
| 'scanArea': scanArea ?? 0 | |||
| @@ -0,0 +1,16 @@ | |||
| import 'barcode_format.dart'; | |||
| /// The [Barcode] object holds information about the barcode or qr code. | |||
| /// | |||
| /// [code] is the content of the barcode. | |||
| /// [format] displays which type the code is. | |||
| /// Only for Android, [rawBytes] gives a list of bytes of the result. | |||
| class Barcode { | |||
| Barcode(this.code, this.format, this.rawBytes); | |||
| final String code; | |||
| final BarcodeFormat format; | |||
| /// Raw bytes are only supported by Android. | |||
| final List<int> rawBytes; | |||
| } | |||
| @@ -0,0 +1,174 @@ | |||
| enum BarcodeFormat { | |||
| /// Aztec 2D barcode format. | |||
| aztec, | |||
| /// CODABAR 1D format. | |||
| codabar, | |||
| /// Code 39 1D format. | |||
| code39, | |||
| /// Code 93 1D format. | |||
| code93, | |||
| /// Code 128 1D format. | |||
| code128, | |||
| /// Data Matrix 2D barcode format. | |||
| dataMatrix, | |||
| /// EAN-8 1D format. | |||
| ean8, | |||
| /// EAN-13 1D format. | |||
| ean13, | |||
| /// ITF (Interleaved Two of Five) 1D format. | |||
| itf, | |||
| /// MaxiCode 2D barcode format. | |||
| maxicode, | |||
| /// PDF417 format. | |||
| pdf417, | |||
| /// QR Code 2D barcode format. | |||
| qrcode, | |||
| /// RSS 14 | |||
| rss14, | |||
| /// RSS EXPANDED | |||
| rssExpanded, | |||
| /// UPC-A 1D format. | |||
| upcA, | |||
| /// UPC-E 1D format. | |||
| upcE, | |||
| /// UPC/EAN extension format. Not a stand-alone format. | |||
| upcEanExtension | |||
| } | |||
| extension BarcodeTypesExtension on BarcodeFormat { | |||
| int asInt() { | |||
| return index; | |||
| } | |||
| static BarcodeFormat fromString(String format) { | |||
| switch (format) { | |||
| case 'AZTEC': | |||
| return BarcodeFormat.aztec; | |||
| break; | |||
| case 'CODABAR': | |||
| return BarcodeFormat.codabar; | |||
| break; | |||
| case 'CODE_39': | |||
| return BarcodeFormat.code39; | |||
| break; | |||
| case 'CODE_93': | |||
| return BarcodeFormat.code93; | |||
| break; | |||
| case 'CODE_128': | |||
| return BarcodeFormat.code128; | |||
| break; | |||
| case 'DATA_MATRIX': | |||
| return BarcodeFormat.dataMatrix; | |||
| break; | |||
| case 'EAN_8': | |||
| return BarcodeFormat.ean8; | |||
| break; | |||
| case 'EAN_13': | |||
| return BarcodeFormat.ean13; | |||
| break; | |||
| case 'ITF': | |||
| return BarcodeFormat.itf; | |||
| break; | |||
| case 'MAXICODE': | |||
| return BarcodeFormat.maxicode; | |||
| break; | |||
| case 'PDF_417': | |||
| return BarcodeFormat.pdf417; | |||
| break; | |||
| case 'QR_CODE': | |||
| return BarcodeFormat.qrcode; | |||
| break; | |||
| case 'RSS14': | |||
| return BarcodeFormat.rss14; | |||
| break; | |||
| case 'RSS_EXPANDED': | |||
| return BarcodeFormat.rssExpanded; | |||
| break; | |||
| case 'UPC_A': | |||
| return BarcodeFormat.upcA; | |||
| break; | |||
| case 'UPC_E': | |||
| return BarcodeFormat.upcE; | |||
| break; | |||
| case 'UPC_EAN_EXTENSION': | |||
| return BarcodeFormat.upcEanExtension; | |||
| break; | |||
| default: | |||
| return null; | |||
| } | |||
| } | |||
| String get formatName { | |||
| switch (this) { | |||
| case BarcodeFormat.aztec: | |||
| return 'AZTEC'; | |||
| break; | |||
| case BarcodeFormat.codabar: | |||
| return 'CODABAR'; | |||
| break; | |||
| case BarcodeFormat.code39: | |||
| return 'CODE_39'; | |||
| break; | |||
| case BarcodeFormat.code93: | |||
| return 'CODE_93'; | |||
| break; | |||
| case BarcodeFormat.code128: | |||
| return 'CODE_128'; | |||
| break; | |||
| case BarcodeFormat.dataMatrix: | |||
| return 'DATA_MATRIX'; | |||
| break; | |||
| case BarcodeFormat.ean8: | |||
| return 'EAN_8'; | |||
| break; | |||
| case BarcodeFormat.ean13: | |||
| return 'EAN_13'; | |||
| break; | |||
| case BarcodeFormat.itf: | |||
| return 'ITF'; | |||
| break; | |||
| case BarcodeFormat.maxicode: | |||
| return 'MAXICODE'; | |||
| break; | |||
| case BarcodeFormat.pdf417: | |||
| return 'PDF_417'; | |||
| break; | |||
| case BarcodeFormat.qrcode: | |||
| return 'QR_CODE'; | |||
| break; | |||
| case BarcodeFormat.rss14: | |||
| return 'RSS14'; | |||
| break; | |||
| case BarcodeFormat.rssExpanded: | |||
| return 'RSS_EXPANDED'; | |||
| break; | |||
| case BarcodeFormat.upcA: | |||
| return 'UPC_A'; | |||
| break; | |||
| case BarcodeFormat.upcE: | |||
| return 'UPC_E'; | |||
| break; | |||
| case BarcodeFormat.upcEanExtension: | |||
| return 'UPC_EAN_EXTENSION'; | |||
| break; | |||
| default: | |||
| return 'NOT_VALID'; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| enum CameraFacing { | |||
| /// Shows back facing camera. | |||
| back, | |||
| /// Shows front facing camera. | |||
| front | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| /// This is thrown when the plugin reports an error. | |||
| class CameraException implements Exception { | |||
| /// Creates a new camera exception with the given error code and description. | |||
| CameraException(this.code, this.description); | |||
| /// Error code. | |||
| String code; | |||
| /// Textual description of the error. | |||
| String description; | |||
| @override | |||
| String toString() => 'CameraException($code, $description)'; | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| class SystemFeatures { | |||
| SystemFeatures(this.hasFlash, this.hasBackCamera, this.hasFrontCamera); | |||
| factory SystemFeatures.fromJson(Map<String, dynamic> features) => | |||
| SystemFeatures( | |||
| features['hasFlash'] ?? false, | |||
| features['hasBackCamera'] ?? false, | |||
| features['hasFrontCamera'] ?? false); | |||
| final bool hasFlash; | |||
| final bool hasFrontCamera; | |||
| final bool hasBackCamera; | |||
| } | |||
| @@ -5,7 +5,7 @@ homepage: https://juliuscanute.com | |||
| repository: https://github.com/juliuscanute/qr_code_scanner | |||
| environment: | |||
| sdk: ">=2.3.0 <3.0.0" | |||
| sdk: ">=2.6.0 <3.0.0" | |||
| flutter: ^1.10.0 | |||
| dependencies: | |||