大幅変更&環境バージョンアップ

This commit is contained in:
2024-08-22 14:35:09 +09:00
parent 56e9861c7a
commit dc58dc0584
446 changed files with 29645 additions and 8315 deletions

View File

@ -0,0 +1,67 @@
# Built application files
*.apk
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

View File

@ -0,0 +1,64 @@
group 'net.touchcapture.qr.flutterqr'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.8.22' //''1.9.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.0' //8.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace 'net.touchcapture.qr.flutterqr'
compileSdk 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// minSdkVersion is determined by Native View.
minSdkVersion 20
targetSdkVersion 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
kotlinOptions {
jvmTarget = '11'
}
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 11
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
if (project.android.hasProperty('namespace')) {
namespace 'net.touchcapture.qr.flutterqr'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.zxing:core:3.5.2'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
}

View File

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

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

View File

@ -0,0 +1,4 @@
<manifest package="net.touchcapture.qr.flutterqr"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
</manifest>

View File

@ -0,0 +1,45 @@
package net.touchcapture.qr.flutterqr
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import com.journeyapps.barcodescanner.BarcodeView
import com.journeyapps.barcodescanner.Size
class CustomFramingRectBarcodeView : BarcodeView {
private var bottomOffset = BOTTOM_OFFSET_NOT_SET_VALUE
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun calculateFramingRect(container: Rect, surface: Rect): Rect {
val containerArea = Rect(container)
val intersects =
containerArea.intersect(surface) //adjusts the containerArea (code from super.calculateFramingRect)
val scanAreaRect = super.calculateFramingRect(container, surface)
if (bottomOffset != BOTTOM_OFFSET_NOT_SET_VALUE) { //if the setFramingRect function was called, then we shift the scan area by Y
val scanAreaRectWithOffset = Rect(scanAreaRect)
scanAreaRectWithOffset.bottom -= bottomOffset
scanAreaRectWithOffset.top -= bottomOffset
val belongsToContainer = scanAreaRectWithOffset.intersect(containerArea)
if (belongsToContainer) {
return scanAreaRectWithOffset
}
}
return scanAreaRect
}
fun setFramingRect(rectWidth: Int, rectHeight: Int, bottomOffset: Int) {
this.bottomOffset = bottomOffset
framingRectSize = Size(rectWidth, rectHeight)
}
companion object {
private const val BOTTOM_OFFSET_NOT_SET_VALUE = -1
}
}

View File

@ -0,0 +1,47 @@
package net.touchcapture.qr.flutterqr
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
class FlutterQrPlugin : FlutterPlugin, ActivityAware {
/** Plugin registration embedding v2 */
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
flutterPluginBinding.platformViewRegistry
.registerViewFactory(
VIEW_TYPE_ID,
QRViewFactory(flutterPluginBinding.binaryMessenger)
)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Leave empty
// Nullifying QrShared.activity and QrShared.binding here will cause errors if plugin is detached by another plugin
}
override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
QrShared.activity = activityPluginBinding.activity
QrShared.binding = activityPluginBinding
}
override fun onDetachedFromActivityForConfigChanges() {
QrShared.activity = null
QrShared.binding = null
}
override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) {
QrShared.activity = activityPluginBinding.activity
QrShared.binding = activityPluginBinding
}
override fun onDetachedFromActivity() {
QrShared.activity = null
QrShared.binding = null
}
companion object {
private const val VIEW_TYPE_ID = "net.touchcapture.qr.flutterqr/qrview"
}
}

View File

@ -0,0 +1,386 @@
package net.touchcapture.qr.flutterqr
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.view.View
import androidx.core.content.ContextCompat
import com.google.zxing.BarcodeFormat
import com.google.zxing.ResultPoint
import com.journeyapps.barcodescanner.BarcodeCallback
import com.journeyapps.barcodescanner.BarcodeResult
import com.journeyapps.barcodescanner.DefaultDecoderFactory
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.platform.PlatformView
class QRView(
private val context: Context,
messenger: BinaryMessenger,
private val id: Int,
private val params: HashMap<String, Any>
) : PlatformView, MethodChannel.MethodCallHandler, PluginRegistry.RequestPermissionsResultListener {
private val cameraRequestCode = QrShared.CAMERA_REQUEST_ID + this.id
private val channel: MethodChannel = MethodChannel(
messenger, "net.touchcapture.qr.flutterqr/qrview_$id"
)
private val cameraFacingBack = 0
private val cameraFacingFront = 1
private var isRequestingPermission = false
private var isTorchOn = false
private var isPaused = false
private var barcodeView: CustomFramingRectBarcodeView? = null
private var unRegisterLifecycleCallback: UnRegisterLifecycleCallback? = null
init {
QrShared.binding?.addRequestPermissionsResultListener(this)
channel.setMethodCallHandler(this)
unRegisterLifecycleCallback = QrShared.activity?.registerLifecycleCallbacks(
onPause = {
if (!isPaused && hasCameraPermission) barcodeView?.pause()
},
onResume = {
if (!hasCameraPermission && !isRequestingPermission) checkAndRequestPermission()
else if (!isPaused && hasCameraPermission) barcodeView?.resume()
}
)
}
override fun dispose() {
unRegisterLifecycleCallback?.invoke()
QrShared.binding?.removeRequestPermissionsResultListener(this)
barcodeView?.pause()
barcodeView = null
}
override fun getView(): View = initBarCodeView()
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
@Suppress("UNCHECKED_CAST")
when (call.method) {
"startScan" -> startScan(call.arguments as? List<Int>, result)
"stopScan" -> stopScan()
"flipCamera" -> flipCamera(result)
"toggleFlash" -> toggleFlash(result)
"pauseCamera" -> pauseCamera(result)
// Stopping camera is the same as pausing camera
"stopCamera" -> pauseCamera(result)
"resumeCamera" -> resumeCamera(result)
"requestPermissions" -> checkAndRequestPermission()
"getCameraInfo" -> getCameraInfo(result)
"getFlashInfo" -> getFlashInfo(result)
"getSystemFeatures" -> getSystemFeatures(result)
"changeScanArea" -> changeScanArea(
dpScanAreaWidth = requireNotNull(call.argument<Double>("scanAreaWidth")),
dpScanAreaHeight = requireNotNull(call.argument<Double>("scanAreaHeight")),
cutOutBottomOffset = requireNotNull(call.argument<Double>("cutOutBottomOffset")),
result = result,
)
"invertScan" -> setInvertScan(
isInvert = call.argument<Boolean>("isInvertScan") ?: false,
)
else -> result.notImplemented()
}
}
private fun initBarCodeView(): CustomFramingRectBarcodeView {
var barcodeView = barcodeView
if (barcodeView == null) {
barcodeView = CustomFramingRectBarcodeView(QrShared.activity).also {
this.barcodeView = it
}
barcodeView.decoderFactory = DefaultDecoderFactory(null, null, null, 2)
if (params[PARAMS_CAMERA_FACING] as Int == 1) {
barcodeView.cameraSettings?.requestedCameraId = cameraFacingFront
}
} else if (!isPaused) {
barcodeView.resume()
}
return barcodeView
}
// region Camera Info
private fun getCameraInfo(result: MethodChannel.Result) {
val barcodeView = barcodeView ?: return barCodeViewNotSet(result)
result.success(barcodeView.cameraSettings.requestedCameraId)
}
private fun getFlashInfo(result: MethodChannel.Result) {
if (barcodeView == null) return barCodeViewNotSet(result)
result.success(isTorchOn)
}
private fun hasFlash(): Boolean {
return hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)
}
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
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 =
context.packageManager.hasSystemFeature(feature)
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("", e.message, null)
}
}
// endregion
// region Camera Controls
private fun flipCamera(result: MethodChannel.Result) {
val barcodeView = barcodeView ?: return barCodeViewNotSet(result)
barcodeView.pause()
val settings = barcodeView.cameraSettings
if (settings.requestedCameraId == cameraFacingFront) {
settings.requestedCameraId = cameraFacingBack
} else settings.requestedCameraId = cameraFacingFront
barcodeView.resume()
result.success(settings.requestedCameraId)
}
private fun toggleFlash(result: MethodChannel.Result) {
val barcodeView = barcodeView ?: return barCodeViewNotSet(result)
if (hasFlash()) {
barcodeView.setTorch(!isTorchOn)
isTorchOn = !isTorchOn
result.success(isTorchOn)
} else {
result.error(ERROR_CODE_NOT_SET, ERROR_MESSAGE_FLASH_NOT_FOUND, null)
}
}
private fun pauseCamera(result: MethodChannel.Result) {
val barcodeView = barcodeView ?: return barCodeViewNotSet(result)
if (barcodeView.isPreviewActive) {
isPaused = true
barcodeView.pause()
}
result.success(true)
}
private fun resumeCamera(result: MethodChannel.Result) {
val barcodeView = barcodeView ?: return barCodeViewNotSet(result)
if (!barcodeView.isPreviewActive) {
isPaused = false
barcodeView.resume()
}
result.success(true)
}
private fun startScan(arguments: List<Int>?, result: MethodChannel.Result) {
checkAndRequestPermission()
val allowedBarcodeTypes = getAllowedBarcodeTypes(arguments, result)
if (arguments == null) {
barcodeView?.decoderFactory = DefaultDecoderFactory(null, null, null, 2)
} else {
barcodeView?.decoderFactory = DefaultDecoderFactory(allowedBarcodeTypes, null, null, 2)
}
barcodeView?.decodeContinuous(
object : BarcodeCallback {
override fun barcodeResult(result: BarcodeResult) {
if (allowedBarcodeTypes.isEmpty() || allowedBarcodeTypes.contains(result.barcodeFormat)) {
val code = mapOf(
"code" to result.text,
"type" to result.barcodeFormat.name,
"rawBytes" to result.rawBytes
)
channel.invokeMethod(CHANNEL_METHOD_ON_RECOGNIZE_QR, code)
}
}
override fun possibleResultPoints(resultPoints: List<ResultPoint>) = Unit
}
)
}
private fun stopScan() {
barcodeView?.stopDecoding()
}
private fun setInvertScan(isInvert: Boolean) {
val barcodeView = barcodeView ?: return
with(barcodeView) {
pause()
cameraSettings.isScanInverted = isInvert
resume()
}
}
private fun changeScanArea(
dpScanAreaWidth: Double,
dpScanAreaHeight: Double,
cutOutBottomOffset: Double,
result: MethodChannel.Result
) {
setScanAreaSize(dpScanAreaWidth, dpScanAreaHeight, cutOutBottomOffset)
result.success(true)
}
private fun setScanAreaSize(
dpScanAreaWidth: Double,
dpScanAreaHeight: Double,
dpCutOutBottomOffset: Double
) {
barcodeView?.setFramingRect(
dpScanAreaWidth.convertDpToPixels(),
dpScanAreaHeight.convertDpToPixels(),
dpCutOutBottomOffset.convertDpToPixels(),
)
}
// endregion
// region permissions
private val hasCameraPermission: Boolean
get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
if (requestCode != cameraRequestCode) return false
isRequestingPermission = false
val permissionGranted =
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED
channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, permissionGranted)
return permissionGranted
}
private fun checkAndRequestPermission() {
if (hasCameraPermission) {
channel.invokeMethod(CHANNEL_METHOD_ON_PERMISSION_SET, true)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isRequestingPermission) {
QrShared.activity?.requestPermissions(
arrayOf(Manifest.permission.CAMERA),
cameraRequestCode
)
}
}
// endregion
// region barcode common
private fun getAllowedBarcodeTypes(
arguments: List<Int>?,
result: MethodChannel.Result
): List<BarcodeFormat> {
return try {
arguments?.map {
BarcodeFormat.values()[it]
}.orEmpty()
} catch (e: Exception) {
result.error("", e.message, null)
emptyList()
}
}
private fun barCodeViewNotSet(result: MethodChannel.Result) {
result.error(
ERROR_CODE_NOT_SET,
ERROR_MESSAGE_NOT_SET,
null
)
}
// endregion
// region helpers
private fun Double.convertDpToPixels() =
(this * context.resources.displayMetrics.density).toInt()
// endregion
companion object {
private const val CHANNEL_METHOD_ON_PERMISSION_SET = "onPermissionSet"
private const val CHANNEL_METHOD_ON_RECOGNIZE_QR = "onRecognizeQR"
private const val PARAMS_CAMERA_FACING = "cameraFacing"
private const val ERROR_CODE_NOT_SET = "404"
private const val ERROR_MESSAGE_NOT_SET = "No barcode view found"
private const val ERROR_MESSAGE_FLASH_NOT_FOUND = "This device doesn't support flash"
}
}

View File

@ -0,0 +1,30 @@
package net.touchcapture.qr.flutterqr
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class QRViewFactory(
private val messenger: BinaryMessenger
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(
context: Context?,
viewId: Int,
args: Any?
): PlatformView {
@Suppress("UNCHECKED_CAST")
val params = args as HashMap<String, Any>
return QRView(
context = requireNotNull(context),
id = viewId,
messenger = messenger,
params = params
)
}
}

View File

@ -0,0 +1,41 @@
package net.touchcapture.qr.flutterqr
import android.app.Activity
import android.app.Application
import android.os.Bundle
class UnRegisterLifecycleCallback(
private val application: Application,
private val callback: Application.ActivityLifecycleCallbacks,
) {
operator fun invoke() = application.unregisterActivityLifecycleCallbacks(callback)
}
fun Activity.registerLifecycleCallbacks(
onPause: (() -> Unit)? = null,
onResume: (() -> Unit)? = null,
): UnRegisterLifecycleCallback {
val callback = object : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(p0: Activity) {
if (p0 == this@registerLifecycleCallbacks) onPause?.invoke()
}
override fun onActivityResumed(p0: Activity) {
if (p0 == this@registerLifecycleCallbacks) onResume?.invoke()
}
override fun onActivityStarted(p0: Activity) = Unit
override fun onActivityDestroyed(p0: Activity) = Unit
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) = Unit
override fun onActivityStopped(p0: Activity) = Unit
override fun onActivityCreated(p0: Activity, p1: Bundle?) = Unit
}
application.registerActivityLifecycleCallbacks(callback)
return UnRegisterLifecycleCallback(application, callback)
}

View File

@ -0,0 +1,15 @@
package net.touchcapture.qr.flutterqr
import android.annotation.SuppressLint
import android.app.Activity
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@SuppressLint("StaticFieldLeak")
object QrShared {
const val CAMERA_REQUEST_ID = 513469796
var activity: Activity? = null
var binding: ActivityPluginBinding? = null
}