Rimossa libreria per la scansione dei barcode tramite fotocamera
This commit is contained in:
parent
45ddca1928
commit
3cf63fc1f1
1
barcode_kaiteki/.gitignore
vendored
1
barcode_kaiteki/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/build
|
||||
@ -1,46 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.1.1"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
kotlinOptions.freeCompilerArgs += ['-module-name', "barcode.kaiteki"]
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
// For Kotlin projects
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha03'
|
||||
|
||||
api 'androidx.camera:camera-core:1.0.0-alpha03'
|
||||
api 'androidx.camera:camera-camera2:1.0.0-alpha03'
|
||||
|
||||
api 'com.google.zxing:core:3.4.0'
|
||||
}
|
||||
21
barcode_kaiteki/proguard-rules.pro
vendored
21
barcode_kaiteki/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@ -1,6 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.kroegerama.kaiteki.bcode">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
</manifest>
|
||||
@ -1,72 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode
|
||||
|
||||
import android.graphics.ImageFormat
|
||||
import android.util.Log
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.MultiFormatReader
|
||||
import com.google.zxing.PlanarYUVLuminanceSource
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
|
||||
internal interface ResultListener {
|
||||
fun onResult(result: Result, imageWidth: Int, imageHeight: Int, imageRotation: Int)
|
||||
fun onNoResult()
|
||||
}
|
||||
|
||||
internal class BarcodeAnalyzer(
|
||||
private val listener: ResultListener,
|
||||
private val reader: MultiFormatReader
|
||||
) : ImageAnalysis.Analyzer {
|
||||
|
||||
var enabled = true
|
||||
var inverted = false
|
||||
|
||||
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
|
||||
if (!enabled) return
|
||||
|
||||
//YUV_420 is normally the input type here, but other YUV types are also supported in theory
|
||||
if (ImageFormat.YUV_420_888 != image.format && ImageFormat.YUV_422_888 != image.format && ImageFormat.YUV_444_888 != image.format) {
|
||||
Log.e(TAG, "Unexpected format: ${image.format}")
|
||||
listener.onNoResult()
|
||||
return
|
||||
}
|
||||
val byteBuffer = image.image?.planes?.firstOrNull()?.buffer
|
||||
if (byteBuffer == null) {
|
||||
listener.onNoResult()
|
||||
return
|
||||
}
|
||||
|
||||
var data = ByteArray(byteBuffer.remaining()).also { byteBuffer.get(it) }
|
||||
|
||||
var width = image.width
|
||||
var height = image.height
|
||||
|
||||
// val rotatedData = ByteArray(data.size)
|
||||
// for (y in 0 until height) {
|
||||
// for (x in 0 until width) rotatedData[x * height + height - y - 1] = data[x + y * width]
|
||||
// }
|
||||
//
|
||||
// data = rotatedData
|
||||
// val tmp = width
|
||||
// width = height
|
||||
// height = tmp
|
||||
|
||||
val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false).let {
|
||||
if (inverted) it.invert() else it
|
||||
}
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
|
||||
try {
|
||||
val result = reader.decodeWithState(bitmap)
|
||||
listener.onResult(result, width, height, rotationDegrees)
|
||||
} catch (e: Exception) {
|
||||
listener.onNoResult()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BarcodeAnalyzer"
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode
|
||||
|
||||
import com.google.zxing.Result
|
||||
|
||||
interface BarcodeResultListener {
|
||||
|
||||
/**
|
||||
* @param result zxing result
|
||||
*
|
||||
* @return return true to dismiss the dialog/fragment
|
||||
*/
|
||||
fun onBarcodeResult(result: Result): Boolean
|
||||
|
||||
fun onBarcodeScanCancelled()
|
||||
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode
|
||||
|
||||
import android.graphics.*
|
||||
import android.media.Image
|
||||
import com.google.zxing.PlanarYUVLuminanceSource
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
internal fun Image.toBitmap(): Bitmap {
|
||||
val yBuffer = planes[0].buffer // Y
|
||||
val uBuffer = planes[1].buffer // U
|
||||
val vBuffer = planes[2].buffer // V
|
||||
|
||||
val ySize = yBuffer.remaining()
|
||||
val uSize = uBuffer.remaining()
|
||||
val vSize = vBuffer.remaining()
|
||||
|
||||
val nv21 = ByteArray(ySize + uSize + vSize)
|
||||
|
||||
//U and V are swapped
|
||||
yBuffer.get(nv21, 0, ySize)
|
||||
vBuffer.get(nv21, ySize, vSize)
|
||||
uBuffer.get(nv21, ySize + vSize, uSize)
|
||||
|
||||
val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
|
||||
val out = ByteArrayOutputStream()
|
||||
yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
|
||||
val imageBytes = out.toByteArray()
|
||||
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
}
|
||||
|
||||
internal fun PlanarYUVLuminanceSource.toBitmap() =
|
||||
Bitmap.createBitmap(renderThumbnail(), thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)
|
||||
@ -1,53 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.TypedArray
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
internal fun AttributeSet?.handleArguments(
|
||||
context: Context, attrs: IntArray, defStyleAttr: Int, defStyleRes: Int,
|
||||
block: TypedArray.() -> Unit
|
||||
) = this?.let {
|
||||
val arr = context.obtainStyledAttributes(it, attrs, defStyleAttr, defStyleRes)
|
||||
block(arr)
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
internal typealias Style = R.styleable
|
||||
|
||||
internal val Context.hasCameraPermission
|
||||
get() = isPermissionGranted(Manifest.permission.CAMERA)
|
||||
|
||||
internal fun Fragment.requestCameraPermission(requestCode: Int) =
|
||||
requestPermission(Manifest.permission.CAMERA, requestCode)
|
||||
|
||||
internal val IntArray.isPermissionGranted
|
||||
get() = size > 0 && get(0) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
private fun Context.isPermissionGranted(permission: String) =
|
||||
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
private fun Fragment.requestPermission(permission: String, requestCode: Int) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
|
||||
if (context?.isPermissionGranted(permission) == true) return
|
||||
requestPermissions(arrayOf(permission), requestCode)
|
||||
}
|
||||
|
||||
internal class Debouncer(
|
||||
val debounceTime: Int
|
||||
) {
|
||||
private var lastShot = 0L
|
||||
|
||||
operator fun <T> invoke(block: () -> T) = if (System.currentTimeMillis() - lastShot > debounceTime) {
|
||||
block.invoke().also {
|
||||
lastShot = System.currentTimeMillis()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.BarcodeResultListener
|
||||
import com.kroegerama.kaiteki.bcode.R
|
||||
import com.kroegerama.kaiteki.bcode.hasCameraPermission
|
||||
import com.kroegerama.kaiteki.bcode.views.BarcodeView
|
||||
|
||||
|
||||
fun Context.showBarcodeAlertDialog(
|
||||
owner: LifecycleOwner,
|
||||
listener: BarcodeResultListener,
|
||||
formats: List<BarcodeFormat> = listOf(BarcodeFormat.QR_CODE),
|
||||
barcodeInverted: Boolean = false
|
||||
) {
|
||||
if (!hasCameraPermission) {
|
||||
Log.w("BarcodeAlertDialog", "Camera permission required")
|
||||
Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
val view = LayoutInflater.from(this).inflate(R.layout.dlg_barcode, null, false)
|
||||
val bcode = view.findViewById<BarcodeView>(R.id.bcode)
|
||||
val handler = Handler()
|
||||
|
||||
val dlg = AlertDialog.Builder(this)
|
||||
.setOnDismissListener { bcode.unbind() }
|
||||
.setView(view)
|
||||
.setOnCancelListener {
|
||||
listener.onBarcodeScanCancelled()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
listener.onBarcodeScanCancelled()
|
||||
}
|
||||
.show()
|
||||
|
||||
bcode.setFormats(formats)
|
||||
bcode.setBarcodeInverted(barcodeInverted)
|
||||
bcode.setBarcodeResultListener(object : BarcodeResultListener {
|
||||
override fun onBarcodeResult(result: Result): Boolean {
|
||||
val doDismiss = listener.onBarcodeResult(result)
|
||||
if (doDismiss) {
|
||||
handler.postDelayed(500) {
|
||||
dlg.dismiss()
|
||||
}
|
||||
}
|
||||
return doDismiss
|
||||
}
|
||||
|
||||
override fun onBarcodeScanCancelled() {
|
||||
//Ignore: BarcodeView will never emit this event
|
||||
}
|
||||
})
|
||||
|
||||
bcode.bindToLifecycle(owner)
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.*
|
||||
import kotlinx.android.synthetic.main.dlg_barcode.*
|
||||
|
||||
class BarcodeBottomSheet : BottomSheetDialogFragment(), BarcodeResultListener {
|
||||
|
||||
private val formats: List<BarcodeFormat>? by lazy {
|
||||
arguments?.getSerializable(KEY_FORMATS) as List<BarcodeFormat>
|
||||
}
|
||||
|
||||
private val barcodeInverted by lazy {
|
||||
arguments?.getBoolean(KEY_INVERTED, false) ?: false
|
||||
}
|
||||
|
||||
private val handler = Handler()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
||||
inflater.inflate(R.layout.dlg_barcode, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
formats?.let(bcode::setFormats)
|
||||
bcode.setBarcodeInverted(barcodeInverted)
|
||||
bcode.setBarcodeResultListener(this)
|
||||
|
||||
if (requireContext().hasCameraPermission) {
|
||||
bcode.bindToLifecycle(this)
|
||||
} else {
|
||||
requestCameraPermission(REQUEST_CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA ->
|
||||
if (grantResults.isPermissionGranted)
|
||||
bcode.bindToLifecycle(this)
|
||||
else
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
bcode.unbind()
|
||||
}
|
||||
|
||||
override fun onBarcodeResult(result: Result): Boolean {
|
||||
if ((parentFragment as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
handler.postDelayed(500) {
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
} else if ((activity as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
handler.postDelayed(500) {
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onBarcodeScanCancelled() {
|
||||
//Ignore: BarcodeView will never emit this event
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
(parentFragment as? BarcodeResultListener)?.onBarcodeScanCancelled()
|
||||
(activity as? BarcodeResultListener)?.onBarcodeScanCancelled()
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_FORMATS = "formats"
|
||||
private const val KEY_INVERTED = "inverted"
|
||||
|
||||
private const val REQUEST_CAMERA = 0xbb_ca
|
||||
|
||||
fun show(
|
||||
fm: FragmentManager,
|
||||
formats: List<BarcodeFormat> = listOf(BarcodeFormat.QR_CODE),
|
||||
barcodeInverted: Boolean = false,
|
||||
tag: String? = null
|
||||
) = BarcodeBottomSheet().apply {
|
||||
arguments = bundleOf(
|
||||
KEY_FORMATS to formats,
|
||||
KEY_INVERTED to barcodeInverted
|
||||
)
|
||||
|
||||
show(fm, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.*
|
||||
import kotlinx.android.synthetic.main.dlg_barcode.*
|
||||
|
||||
open class BarcodeDialog : DialogFragment(), BarcodeResultListener {
|
||||
|
||||
private val formats: List<BarcodeFormat>? by lazy {
|
||||
arguments?.getSerializable(KEY_FORMATS) as List<BarcodeFormat>
|
||||
}
|
||||
|
||||
private val barcodeInverted by lazy {
|
||||
arguments?.getBoolean(KEY_INVERTED, false) ?: false
|
||||
}
|
||||
|
||||
private val handler = Handler()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
||||
inflater.inflate(R.layout.dlg_barcode, container, false).also {
|
||||
dialog?.window?.run {
|
||||
requestFeature(Window.FEATURE_NO_TITLE)
|
||||
requestFeature(Window.FEATURE_SWIPE_TO_DISMISS)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
formats?.let(bcode::setFormats)
|
||||
bcode.setBarcodeInverted(barcodeInverted)
|
||||
bcode.setBarcodeResultListener(this)
|
||||
|
||||
if (requireContext().hasCameraPermission) {
|
||||
bcode.bindToLifecycle(this)
|
||||
} else {
|
||||
requestCameraPermission(REQUEST_CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA ->
|
||||
if (grantResults.isPermissionGranted)
|
||||
bcode.bindToLifecycle(this)
|
||||
else
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onBarcodeScanCancelled() {
|
||||
//Ignore: BarcodeView will never emit this event
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
bcode.unbind()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
}
|
||||
|
||||
override fun onBarcodeResult(result: Result): Boolean {
|
||||
if ((parentFragment as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
handler.postDelayed(500) {
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
} else if ((activity as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
handler.postDelayed(500) {
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
(parentFragment as? BarcodeResultListener)?.onBarcodeScanCancelled()
|
||||
(activity as? BarcodeResultListener)?.onBarcodeScanCancelled()
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_FORMATS = "formats"
|
||||
private const val KEY_INVERTED = "inverted"
|
||||
|
||||
private const val REQUEST_CAMERA = 0xbd_ca
|
||||
|
||||
fun show(
|
||||
fm: FragmentManager,
|
||||
formats: List<BarcodeFormat> = listOf(BarcodeFormat.QR_CODE),
|
||||
barcodeInverted: Boolean = false,
|
||||
tag: String? = null
|
||||
) = BarcodeDialog().apply {
|
||||
arguments = bundleOf(
|
||||
KEY_FORMATS to formats,
|
||||
KEY_INVERTED to barcodeInverted
|
||||
)
|
||||
|
||||
show(fm, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.*
|
||||
import kotlinx.android.synthetic.main.dlg_barcode.*
|
||||
|
||||
class BarcodeFragment : Fragment(), BarcodeResultListener {
|
||||
|
||||
private val formats: List<BarcodeFormat>? by lazy {
|
||||
arguments?.getSerializable(KEY_FORMATS) as List<BarcodeFormat>
|
||||
}
|
||||
|
||||
private val barcodeInverted by lazy {
|
||||
arguments?.getBoolean(KEY_INVERTED, false) ?: false
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
||||
inflater.inflate(R.layout.dlg_barcode, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
formats?.let(bcode::setFormats)
|
||||
bcode.setBarcodeInverted(barcodeInverted)
|
||||
bcode.setBarcodeResultListener(this)
|
||||
|
||||
if (requireContext().hasCameraPermission) {
|
||||
bcode.bindToLifecycle(this)
|
||||
} else {
|
||||
requestCameraPermission(REQUEST_CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA ->
|
||||
if (grantResults.isPermissionGranted)
|
||||
bcode.bindToLifecycle(this)
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
bcode.unbind()
|
||||
}
|
||||
|
||||
override fun onBarcodeResult(result: Result): Boolean {
|
||||
if ((parentFragment as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
return true
|
||||
} else if ((activity as? BarcodeResultListener)?.onBarcodeResult(result) == true) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onBarcodeScanCancelled() {
|
||||
//Ignore: BarcodeView will never emit this event
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_FORMATS = "formats"
|
||||
private const val KEY_INVERTED = "inverted"
|
||||
|
||||
private const val REQUEST_CAMERA = 0xbf_ca
|
||||
|
||||
fun makeInstance(
|
||||
formats: List<BarcodeFormat> = listOf(BarcodeFormat.QR_CODE),
|
||||
barcodeInverted: Boolean = false
|
||||
) = BarcodeFragment().apply {
|
||||
arguments = bundleOf(
|
||||
KEY_FORMATS to formats,
|
||||
KEY_INVERTED to barcodeInverted
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,197 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.views
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.util.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.TextureView
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.camera.core.*
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.MultiFormatReader
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.*
|
||||
import com.kroegerama.kaiteki.bcode.R
|
||||
import kotlin.math.max
|
||||
|
||||
class BarcodeView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr), ResultListener {
|
||||
|
||||
private val textureView: TextureView
|
||||
private val resultView: ResultPointView
|
||||
|
||||
private var bufferSize = SizeF(0f, 0f)
|
||||
|
||||
private var listener: BarcodeResultListener? = null
|
||||
|
||||
private val barcodeReader by lazy { MultiFormatReader() }
|
||||
|
||||
private val analyzer by lazy { BarcodeAnalyzer(this, barcodeReader) }
|
||||
|
||||
private val resultDebouncer = Debouncer(500)
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context).inflate(R.layout.barcode_view, this)
|
||||
|
||||
keepScreenOn = true
|
||||
|
||||
textureView = findViewById(R.id.textureView)
|
||||
resultView = findViewById(R.id.resultView)
|
||||
|
||||
attrs.handleArguments(context, Style.BarcodeView, defStyleAttr, 0) {
|
||||
resultView.showResultPoints = getBoolean(Style.BarcodeView_showResultPoints, true)
|
||||
resultView.setResultPointColor(getColor(Style.BarcodeView_resultPointColor, Color.GREEN))
|
||||
val defaultSize =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, Resources.getSystem().displayMetrics)
|
||||
resultView.setPointSize(getDimension(Style.BarcodeView_resultPointSize, defaultSize))
|
||||
analyzer.inverted = getBoolean(Style.BarcodeView_barcodeInverted, false)
|
||||
}
|
||||
|
||||
textureView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
updateTransform()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResult(result: Result, imageWidth: Int, imageHeight: Int, imageRotation: Int) {
|
||||
resultView.setResult(result, imageWidth, imageHeight, imageRotation)
|
||||
|
||||
val d = resultDebouncer {
|
||||
listener?.onBarcodeResult(result)
|
||||
}
|
||||
if (d == true) {
|
||||
// dialog/fragment will be dismissed -> do not send any more events
|
||||
listener = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNoResult() {
|
||||
resultView.clear()
|
||||
}
|
||||
|
||||
fun setBarcodeResultListener(listener: BarcodeResultListener) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
/**
|
||||
* enable scanning of inverted barcodes (e.g. white QR Code on black background)
|
||||
*/
|
||||
fun setBarcodeInverted(inverted: Boolean) {
|
||||
analyzer.inverted = inverted
|
||||
}
|
||||
|
||||
fun bindToLifecycle(owner: LifecycleOwner) {
|
||||
textureView.post { startPreview(owner) }
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
resultView.clear()
|
||||
listener = null
|
||||
CameraX.unbindAll()
|
||||
}
|
||||
|
||||
fun setFormats(formats: List<BarcodeFormat>) = barcodeReader.setHints(
|
||||
mapOf(
|
||||
DecodeHintType.POSSIBLE_FORMATS to formats
|
||||
)
|
||||
)
|
||||
|
||||
private fun startPreview(owner: LifecycleOwner) {
|
||||
val metrics = DisplayMetrics().also { textureView.display.getRealMetrics(it) }
|
||||
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)
|
||||
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
|
||||
val screenRotation = textureView.display.rotation
|
||||
|
||||
val previewConfig = PreviewConfig.Builder().apply {
|
||||
setLensFacing(CameraX.LensFacing.BACK)
|
||||
setTargetResolution(screenSize / 2)
|
||||
setTargetAspectRatio(screenAspectRatio)
|
||||
setTargetRotation(screenRotation)
|
||||
}.build()
|
||||
|
||||
val preview = Preview(previewConfig).apply {
|
||||
setOnPreviewOutputUpdateListener(::previewOutputUpdated)
|
||||
}
|
||||
|
||||
val analysisConfig = ImageAnalysisConfig.Builder().apply {
|
||||
setLensFacing(CameraX.LensFacing.BACK)
|
||||
setTargetResolution(screenSize / 2)
|
||||
setTargetAspectRatio(screenAspectRatio)
|
||||
setTargetRotation(textureView.display.rotation)
|
||||
|
||||
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
|
||||
val analyzerThread = HandlerThread("BarcodeAnalyzer").apply { start() }
|
||||
setCallbackHandler(Handler(analyzerThread.looper))
|
||||
}.build()
|
||||
|
||||
val analysis = ImageAnalysis(analysisConfig).apply { analyzer = this@BarcodeView.analyzer }
|
||||
|
||||
CameraX.bindToLifecycle(owner, preview, analysis)
|
||||
}
|
||||
|
||||
private fun previewOutputUpdated(output: Preview.PreviewOutput) {
|
||||
// https://github.com/android/camera/blob/848cf1e2c8404599050d79086dee1d0c8951b66e/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/utils/AutoFitPreviewBuilder.kt#L100
|
||||
(textureView.parent as? ViewGroup)?.apply {
|
||||
val idx = indexOfChild(textureView)
|
||||
removeView(textureView)
|
||||
addView(textureView, idx)
|
||||
textureView.surfaceTexture = output.surfaceTexture
|
||||
}
|
||||
|
||||
bufferSize = SizeF(output.textureSize.height.toFloat(), output.textureSize.width.toFloat())
|
||||
updateTransform()
|
||||
}
|
||||
|
||||
private fun updateTransform() {
|
||||
val viewFinderWidth = textureView.width.toFloat()
|
||||
val viewFinderHeight = textureView.height.toFloat()
|
||||
val viewFinderRotation = when (textureView.display.rotation) {
|
||||
Surface.ROTATION_0 -> 0
|
||||
Surface.ROTATION_90 -> 90
|
||||
Surface.ROTATION_180 -> 180
|
||||
Surface.ROTATION_270 -> 270
|
||||
else -> return
|
||||
}
|
||||
val matrix = Matrix()
|
||||
val centerX = viewFinderWidth / 2f
|
||||
val centerY = viewFinderHeight / 2f
|
||||
|
||||
matrix.postRotate(-viewFinderRotation.toFloat(), centerX, centerY)
|
||||
|
||||
val bufferRatio = bufferSize.width / bufferSize.height
|
||||
val viewRatio = viewFinderWidth / viewFinderHeight
|
||||
|
||||
if (bufferRatio > viewRatio) {
|
||||
val factor = bufferRatio / viewRatio
|
||||
matrix.preScale(factor, 1f, centerX, centerY)
|
||||
} else {
|
||||
val factor = viewRatio / bufferRatio
|
||||
matrix.preScale(1f, factor, centerX, centerY)
|
||||
}
|
||||
|
||||
if (viewFinderRotation % 180 != 0) {
|
||||
if (bufferRatio > viewRatio) {
|
||||
val s = 1f / bufferRatio
|
||||
matrix.preScale(s, s, centerX, centerY)
|
||||
} else {
|
||||
val s = max(bufferRatio, 1f / viewRatio)
|
||||
matrix.preScale(s, s, centerX, centerY)
|
||||
}
|
||||
}
|
||||
|
||||
// val dbgScale = .95f
|
||||
// matrix.preScale(dbgScale, dbgScale, centerX, centerY)
|
||||
|
||||
textureView.setTransform(matrix)
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun Size.div(other: Int): Size = Size(width / other, height / other)
|
||||
@ -1,102 +0,0 @@
|
||||
package com.kroegerama.kaiteki.bcode.views
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import com.google.zxing.Result
|
||||
import com.kroegerama.kaiteki.bcode.BuildConfig
|
||||
import com.kroegerama.kaiteki.bcode.Style
|
||||
import com.kroegerama.kaiteki.bcode.handleArguments
|
||||
import kotlin.math.max
|
||||
|
||||
class ResultPointView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private val pPoints = Paint().apply {
|
||||
style = Paint.Style.STROKE
|
||||
color = Color.GREEN
|
||||
strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, Resources.getSystem().displayMetrics)
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
private var resultPoints = floatArrayOf()
|
||||
private var rect = RectF()
|
||||
|
||||
var showResultPoints = true
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
init {
|
||||
attrs.handleArguments(context, Style.ResultPointView, defStyleAttr, 0) {
|
||||
showResultPoints = getBoolean(Style.ResultPointView_showResultPoints, showResultPoints)
|
||||
|
||||
pPoints.color = getColor(Style.ResultPointView_resultPointColor, pPoints.color)
|
||||
pPoints.strokeWidth =
|
||||
getDimension(Style.ResultPointView_resultPointSize, pPoints.strokeWidth)
|
||||
}
|
||||
}
|
||||
|
||||
fun setResultPointColor(@ColorInt color: Int) {
|
||||
pPoints.color = color
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setPointSize(size: Float) {
|
||||
pPoints.strokeWidth = size
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
resultPoints = floatArrayOf()
|
||||
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
fun setResult(result: Result, imageWidth: Int, imageHeight: Int, imageRotation: Int) {
|
||||
if (!showResultPoints) return
|
||||
|
||||
val localMatrix = createMatrix(imageWidth.toFloat(), imageHeight.toFloat(), imageRotation)
|
||||
|
||||
resultPoints = result.resultPoints.flatMap { listOf(it.x, it.y) }.toFloatArray()
|
||||
localMatrix.mapPoints(resultPoints)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
rect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat())
|
||||
localMatrix.mapRect(rect)
|
||||
}
|
||||
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
private fun createMatrix(imageWidth: Float, imageHeight: Float, imageRotation: Int) = Matrix().apply {
|
||||
preTranslate((width - imageWidth) / 2f, (height - imageHeight) / 2f)
|
||||
preRotate(imageRotation.toFloat(), imageWidth / 2f, imageHeight / 2f)
|
||||
|
||||
val wScale: Float
|
||||
val hScale: Float
|
||||
|
||||
if (imageRotation % 180 == 0) {
|
||||
wScale = width.toFloat() / imageWidth
|
||||
hScale = height.toFloat() / imageHeight
|
||||
} else {
|
||||
wScale = height.toFloat() / imageWidth
|
||||
hScale = width.toFloat() / imageHeight
|
||||
|
||||
}
|
||||
|
||||
val scale = max(wScale, hScale)
|
||||
preScale(scale, scale, imageWidth / 2f, imageHeight / 2f)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (showResultPoints) canvas.drawPoints(resultPoints, pPoints)
|
||||
|
||||
if (BuildConfig.DEBUG) canvas.drawRect(rect, pPoints)
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="android.widget.FrameLayout">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/textureView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.kroegerama.kaiteki.bcode.views.ResultPointView
|
||||
android:id="@+id/resultView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</merge>
|
||||
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.kroegerama.kaiteki.bcode.views.BarcodeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/bcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:resultPointColor="#09E85E"
|
||||
app:resultPointSize="8dp"
|
||||
app:showResultPoints="true" />
|
||||
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<attr name="showResultPoints" format="boolean" />
|
||||
<attr name="resultPointColor" format="color" />
|
||||
<attr name="resultPointSize" format="dimension" />
|
||||
|
||||
<declare-styleable name="ResultPointView">
|
||||
<attr name="showResultPoints" />
|
||||
<attr name="resultPointColor" />
|
||||
<attr name="resultPointSize" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="BarcodeView">
|
||||
<attr name="showResultPoints" />
|
||||
<attr name="resultPointColor" />
|
||||
<attr name="resultPointSize" />
|
||||
<attr name="barcodeInverted" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
</resources>
|
||||
@ -10,7 +10,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.0'
|
||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.google.gms:google-services:4.3.5'
|
||||
classpath 'com.google.firebase:perf-plugin:1.3.5'
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
include ':dynamic_gramm'
|
||||
include ':app', ':pointmobilescannerlibrary', ':dynamic_vgalimenti', ':dynamic__base', ':zebrascannerlibrary', ':honeywellscannerlibrary', ':dynamic_ime', ':dynamic_frudis', ':dynamic_saporiveri_pv', ':keyobardemulatorscannerlibrary', ':barcode_base_android_library', ':dynamic_saporiveri', ':barcode_kaiteki'
|
||||
include ':app', ':pointmobilescannerlibrary', ':dynamic_vgalimenti', ':dynamic__base', ':zebrascannerlibrary', ':honeywellscannerlibrary', ':dynamic_ime', ':dynamic_frudis', ':dynamic_saporiveri_pv', ':keyobardemulatorscannerlibrary', ':barcode_base_android_library', ':dynamic_saporiveri'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user