diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt index 53df56e..958a8da 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/FlutterQrPlugin.kt @@ -63,7 +63,6 @@ class FlutterQrPlugin : FlutterPlugin, ActivityAware { inner class CameraRequestPermissionsListener : PluginRegistry.RequestPermissionsResultListener { override fun onRequestPermissionsResult(id: Int, permissions: Array, grantResults: IntArray): Boolean { if (id == Shared.CAMERA_REQUEST_ID && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Shared.cameraPermissionContinuation?.run() return true } return false diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt index 48329c5..91ad6f3 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRView.kt @@ -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) : 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, 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?, result: MethodChannel.Result) { + val allowedBarcodeTypes = mutableListOf() + 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) {} } ) - 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) + } } } } diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt index a797084..e16df1d 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/QRViewFactory.kt @@ -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 + return QRView(messenger, id, params) } } \ No newline at end of file diff --git a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt index 24bc4ab..a5a663d 100644 --- a/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt +++ b/android/src/main/kotlin/net/touchcapture/qr/flutterqr/Shared.kt @@ -5,5 +5,4 @@ import android.app.Activity object Shared { const val CAMERA_REQUEST_ID = 513469796 var activity: Activity? = null - var cameraPermissionContinuation: Runnable? = null } \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore index 47e0b4d..4736dd6 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -9,6 +9,7 @@ .buildlog/ .history .svn/ +.last_build_id # IntelliJ related *.iml diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d28e5f5..3d2b2af 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; + 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 = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 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 = ""; }; - 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 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 = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 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 = ""; }; + 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 = ""; }; /* 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 = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -87,7 +79,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, D7C58E57CAF69D9A14CC212F /* Pods */, - 509AC3E235E568B6DDAAEC7D /* Frameworks */, + AF031A634C9742F16BF4F89A /* Frameworks */, ); sourceTree = ""; }; @@ -122,12 +114,20 @@ name = "Supporting Files"; sourceTree = ""; }; + AF031A634C9742F16BF4F89A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 75B64521967C76C845268E71 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 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 = ""; @@ -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)", diff --git a/example/lib/main.dart b/example/lib/main.dart index beb8d51..13072f0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -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 { Barcode result; - var flashState = flashOn; - var cameraState = frontCamera; QRViewController controller; final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); @@ -64,44 +57,33 @@ class _QRViewExampleState extends State { 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 { ); } - 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 { : 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( - 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; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6df5ea4..929acf1 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -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: diff --git a/ios/Classes/QRView.swift b/ios/Classes/QRView.swift index 298f7a9..e2c65b6 100644 --- a/ios/Classes/QRView.swift +++ b/ios/Classes/QRView.swift @@ -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){ 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 self?.setDimensions(width: arguments["width"] ?? 0, height: arguments["height"] ?? 0, scanArea: arguments["scanArea"] ?? 0) + case "startScan": + self?.startScan(call.arguments as! Array, 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, _ result: @escaping FlutterResult) -> Void { + scanner = MTBBarcodeScanner(previewView: previewView) + + var allowedBarcodeTypes: Array = [] + 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)) } -} + + } diff --git a/ios/Classes/QRViewFactory.swift b/ios/Classes/QRViewFactory.swift index fff745b..6f851f6 100644 --- a/ios/Classes/QRViewFactory.swift +++ b/ios/Classes/QRViewFactory.swift @@ -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 - 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 + return QRView(withFrame: frame, withRegistrar: registrar!,withId: viewId, params: params) } public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { diff --git a/lib/qr_code_scanner.dart b/lib/qr_code_scanner.dart index bc2f4d9..4220613 100644 --- a/lib/qr_code_scanner.dart +++ b/lib/qr_code_scanner.dart @@ -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'; diff --git a/lib/src/qr_code_scanner.dart b/lib/src/qr_code_scanner.dart index 7a60428..eec48b1 100644 --- a/lib/src/qr_code_scanner.dart +++ b/lib/src/qr_code_scanner.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 = { - '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 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 formatsAllowed; + @override State createState() => _QRViewState(); } class _QRViewState extends State { + 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 { 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 { } 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 toMap() { return { - '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; - 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; + 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 _scanUpdateController = StreamController(); Stream 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 _startScan(GlobalKey key, double cutOutSize, + List 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 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 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 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 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 pauseCamera() async { + try { + await _channel.invokeMethod('pauseCamera'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resumes barcode scanning + Future 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 getSystemFeatures() async { + try { + var features = + await _channel.invokeMapMethod('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 diff --git a/lib/src/types/barcode.dart b/lib/src/types/barcode.dart new file mode 100644 index 0000000..9bfffdb --- /dev/null +++ b/lib/src/types/barcode.dart @@ -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 rawBytes; +} diff --git a/lib/src/types/barcode_format.dart b/lib/src/types/barcode_format.dart new file mode 100644 index 0000000..645d448 --- /dev/null +++ b/lib/src/types/barcode_format.dart @@ -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'; + } + } +} diff --git a/lib/src/types/camera.dart b/lib/src/types/camera.dart new file mode 100644 index 0000000..532f3b4 --- /dev/null +++ b/lib/src/types/camera.dart @@ -0,0 +1,7 @@ +enum CameraFacing { + /// Shows back facing camera. + back, + + /// Shows front facing camera. + front +} diff --git a/lib/src/types/camera_exception.dart b/lib/src/types/camera_exception.dart new file mode 100644 index 0000000..740f4b6 --- /dev/null +++ b/lib/src/types/camera_exception.dart @@ -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)'; +} diff --git a/lib/src/types/features.dart b/lib/src/types/features.dart new file mode 100644 index 0000000..0c3618a --- /dev/null +++ b/lib/src/types/features.dart @@ -0,0 +1,13 @@ +class SystemFeatures { + SystemFeatures(this.hasFlash, this.hasBackCamera, this.hasFrontCamera); + + factory SystemFeatures.fromJson(Map features) => + SystemFeatures( + features['hasFlash'] ?? false, + features['hasBackCamera'] ?? false, + features['hasFrontCamera'] ?? false); + + final bool hasFlash; + final bool hasFrontCamera; + final bool hasBackCamera; +} diff --git a/pubspec.yaml b/pubspec.yaml index 1fe733a..88f840a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: