Implementato completamente lo scan del barcode tramite fotocamera.

This commit is contained in:
Giuseppe Scorrano 2020-03-25 19:02:14 +01:00
parent e5a4113968
commit cbce52fd83
35 changed files with 1184 additions and 74 deletions

View File

@ -99,9 +99,9 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.4.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
implementation 'com.squareup.okhttp3:okhttp:4.4.1'
implementation 'com.squareup.retrofit2:retrofit:2.8.0'
implementation 'com.squareup.retrofit2:converter-gson:2.8.0'
implementation 'com.annimon:stream:1.2.1'
implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
@ -109,7 +109,7 @@ dependencies {
implementation 'org.apache.commons:commons-text:1.8'
//MVVM
def dagger2_version = "2.26"
def dagger2_version = '2.27'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
api "com.google.dagger:dagger:$dagger2_version"
@ -145,8 +145,8 @@ dependencies {
implementation project(':barcode_base_android_library')
implementation project(':honeywellscannerlibrary')
implementation project(':keyobardemulatorscannerlibrary')
implementation 'com.kroegerama:barcode-kaiteki:1.1.1'
androidTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
implementation project(':barcode_kaiteki')
androidTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1'
androidTestImplementation 'org.testng:testng:7.1.0'
}
repositories {

View File

@ -4,13 +4,26 @@ import android.app.Dialog;
import android.content.Context;
import android.widget.LinearLayout;
import androidx.fragment.app.DialogFragment;
public class UtilityDialog {
public static void setTo90PercentSize(Context context, Dialog dialog) {
public static void setTo90PercentWidth(Context context, Dialog dialog) {
int width = (int)(context.getResources().getDisplayMetrics().widthPixels*0.90);
//int height = (int)(context.getResources().getDisplayMetrics().heightPixels*0.90);
dialog.getWindow().setLayout(width, LinearLayout.LayoutParams.WRAP_CONTENT);
}
public static void setTo90PercentWidth(Context context, DialogFragment dialog) {
int width = (int)(context.getResources().getDisplayMetrics().widthPixels*0.90);
dialog.getDialog().getWindow().setLayout(width, LinearLayout.LayoutParams.WRAP_CONTENT);
}
// public static void setTo90PercentHeight(Context context, Dialog dialog) {
// int height = (int)(context.getResources().getDisplayMetrics().heightPixels*0.90);
//
// dialog.getWindow().setLayout(width, LinearLayout.LayoutParams.WRAP_CONTENT);
// }
}

View File

@ -6,8 +6,6 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.text.Editable;
import android.text.Html;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@ -23,7 +21,6 @@ import it.integry.integrywmsnative.core.utility.UtilityFocus;
import it.integry.integrywmsnative.core.utility.UtilityProgress;
import it.integry.integrywmsnative.databinding.DialogPvEditArticoloBinding;
import it.integry.integrywmsnative.gest.pv_ordine_acquisto_edit.helper.PVEditOrderHelper;
import it.integry.integrywmsnative.view.dialogs.base.DialogSimpleMessageHelper;
public class EditArticoloDialog {
@ -52,7 +49,7 @@ public class EditArticoloDialog {
mDialog = new BaseDialog(context);
mDialog.setContentView(mBinding.getRoot());
UtilityDialog.setTo90PercentSize(mContext, mDialog);
UtilityDialog.setTo90PercentWidth(mContext, mDialog);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
int qtaCnf = (int)Math.ceil(mArticolo.getQtaOrd() / mArticolo.getQtaCnf());

View File

@ -50,6 +50,7 @@ import it.integry.integrywmsnative.gest.spedizione_new.core.SpedizioneListModel;
import it.integry.integrywmsnative.gest.spedizione_new.model.PickingObjectDTO;
import it.integry.integrywmsnative.gest.vendita.dto.OrdineVenditaInevasoDTO;
import it.integry.integrywmsnative.view.dialogs.base.DialogSimpleInputHelper;
import it.integry.integrywmsnative.view.dialogs.camera_barcode_reader.DialogCameraBarcodeReader;
import it.integry.integrywmsnative.view.dialogs.input_quantity_v2.DialogInputQuantityV2;
import it.integry.integrywmsnative.view.dialogs.input_quantity_v2.DialogInputQuantityV2DTO;
@ -281,24 +282,10 @@ public class SpedizioneActivity extends AppCompatActivity implements SpedizioneV
}
public void startCameraBarcode() {
List<BarcodeFormat> availableBarcodeFormats = new ArrayList<>();
availableBarcodeFormats.add(BarcodeFormat.QR_CODE);
availableBarcodeFormats.add(BarcodeFormat.CODE_128);
availableBarcodeFormats.add(BarcodeFormat.UPC_E);
availableBarcodeFormats.add(BarcodeFormat.CODABAR);
availableBarcodeFormats.add(BarcodeFormat.EAN_8);
availableBarcodeFormats.add(BarcodeFormat.EAN_13);
availableBarcodeFormats.add(BarcodeFormat.ITF);
availableBarcodeFormats.add(BarcodeFormat.UPC_A);
BarcodeBottomSheet bbs = BarcodeBottomSheet.Companion.show(
getSupportFragmentManager(),
availableBarcodeFormats,
false,
"tag"
);
DialogCameraBarcodeReader.newInstance(this, data -> {
this.onScanSuccessful.run(data);
})
.show(getSupportFragmentManager(), "camera_barcode");
}
public void removeListFilter() {

View File

@ -31,7 +31,7 @@ public class DialogAskAction {
mDialog = new BaseDialog(context);
mDialog.setContentView(mBinding.getRoot());
UtilityDialog.setTo90PercentSize(mContext, mDialog);
UtilityDialog.setTo90PercentWidth(mContext, mDialog);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mBinding.bottomSheetActionsTitle.setText(title);

View File

@ -2,28 +2,20 @@ package it.integry.integrywmsnative.view.dialogs.ask_cliente;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.LinearLayout;
import androidx.databinding.DataBindingUtil;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import it.integry.integrywmsnative.R;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.expansion.RunnableArgss;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.MtbDepoPosizione;
import it.integry.integrywmsnative.core.model.VtbDest;
import it.integry.integrywmsnative.core.utility.UtilityDialog;
import it.integry.integrywmsnative.databinding.DialogAskClienteBinding;
@ -68,7 +60,7 @@ public class DialogAskCliente {
mDialog.setContentView(mBinding.getRoot());
mDialog.setCanceledOnTouchOutside(false);
// mDialog.setCancelable(false);
UtilityDialog.setTo90PercentSize(mContext, mDialog);
UtilityDialog.setTo90PercentWidth(mContext, mDialog);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

View File

@ -1,7 +1,6 @@
package it.integry.integrywmsnative.view.dialogs.ask_position_of_lu;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import androidx.appcompat.widget.AppCompatTextView;
@ -85,7 +84,7 @@ public class DialogAskPositionOfLU {
// mDialog.setCancelable(false);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
UtilityDialog.setTo90PercentSize(mContext, mDialog);
UtilityDialog.setTo90PercentWidth(mContext, mDialog);
mDialog.setOnShowListener(dialog -> {
((AppCompatTextView) adapter.getPage(0).findViewById(R.id.description_text)).setText(mCheckForLineaProd ? R.string.ask_production_line_of_lu_message : R.string.ask_position_of_lu_message);

View File

@ -1,31 +1,17 @@
package it.integry.integrywmsnative.view.dialogs.base;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import java.util.HashMap;
import it.integry.integrywmsnative.R;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.utility.UtilityDialog;
import it.integry.integrywmsnative.databinding.DialogBaseBinding;
import it.integry.integrywmsnative.databinding.DialogInputGeneralBinding;
/**
@ -60,7 +46,7 @@ public class DialogSimpleInputHelper {
dialog.setCanceledOnTouchOutside(false);
dialog.setContentView(mBinding.getRoot());
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
UtilityDialog.setTo90PercentSize(mContext,dialog);
UtilityDialog.setTo90PercentWidth(mContext,dialog);
return dialog;
}

View File

@ -2,16 +2,11 @@ package it.integry.integrywmsnative.view.dialogs.basket_lu;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.AbstractMap;
import java.util.ArrayList;
@ -21,12 +16,8 @@ import java.util.Map;
import it.integry.integrywmsnative.R;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.utility.UtilityDialog;
import it.integry.integrywmsnative.databinding.DialogBasketLuBinding;
import it.integry.integrywmsnative.view.dialogs.ask_cliente.viewmodel.DialogAskCliente_Page1ViewModel;
import it.integry.integrywmsnative.view.dialogs.ask_cliente.viewmodel.DialogAskCliente_Page2ViewModel;
import it.integry.integrywmsnative.view.dialogs.basket_lu.pages.page1.DialogBasketLU_Page1_ListAdapter;
import it.integry.integrywmsnative.view.dialogs.basket_lu.pages.page1.DialogBasketLU_Page1_ViewModel;
import it.integry.integrywmsnative.view.dialogs.basket_lu.pages.page2.DialogBasketLU_Page2_ViewModel;
@ -57,7 +48,7 @@ public class DialogBasketLU {
mDialog.setCanceledOnTouchOutside(false);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
UtilityDialog.setTo90PercentSize(context, mDialog);
UtilityDialog.setTo90PercentWidth(context, mDialog);
this.initViewPager();
}

View File

@ -0,0 +1,129 @@
package it.integry.integrywmsnative.view.dialogs.camera_barcode_reader;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.DialogFragment;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.kroegerama.kaiteki.bcode.BarcodeResultListener;
import com.orhanobut.logger.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.barcode_base_android_library.model.BarcodeType;
import it.integry.integrywmsnative.R;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.utility.UtilityDialog;
import it.integry.integrywmsnative.databinding.DialogCameraBarcodeReaderBinding;
public class DialogCameraBarcodeReader extends DialogFragment implements BarcodeResultListener {
private Context mContext;
private DialogCameraBarcodeReaderBinding mBindings;
private final RunnableArgs<BarcodeScanDTO> onComplete;
public static DialogCameraBarcodeReader newInstance(@NonNull Context context, @NonNull final RunnableArgs<BarcodeScanDTO> onComplete) {
return new DialogCameraBarcodeReader(context, onComplete);
}
private DialogCameraBarcodeReader(@NonNull Context context, @NonNull final RunnableArgs<BarcodeScanDTO> onComplete){
this.mContext = context;
this.onComplete = onComplete;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBindings = DataBindingUtil.inflate(inflater, R.layout.dialog_camera_barcode_reader, container, false);
UtilityDialog.setTo90PercentWidth(mContext, this);
getDialog().setCanceledOnTouchOutside(false);
getDialog().setCancelable(false);
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
this.initBarcodeView();
mBindings.buttonConfirm.setOnClickListener(v -> {
dismiss();
});
return mBindings.getRoot();
}
private void initBarcodeView() {
List<BarcodeFormat> availableBarcodeFormats = new ArrayList<>();
availableBarcodeFormats.add(BarcodeFormat.QR_CODE);
availableBarcodeFormats.add(BarcodeFormat.CODE_128);
availableBarcodeFormats.add(BarcodeFormat.UPC_E);
availableBarcodeFormats.add(BarcodeFormat.CODABAR);
availableBarcodeFormats.add(BarcodeFormat.EAN_8);
availableBarcodeFormats.add(BarcodeFormat.EAN_13);
availableBarcodeFormats.add(BarcodeFormat.ITF);
availableBarcodeFormats.add(BarcodeFormat.UPC_A);
mBindings.bcode.setFormats(availableBarcodeFormats);
mBindings.bcode.setBarcodeInverted(false);
mBindings.bcode.setBarcodeResultListener(this);
mBindings.bcode.bindToLifecycle(this);
}
@Override
public boolean onBarcodeResult(@NotNull Result result) {
BarcodeScanDTO barcodeScanDTO = new BarcodeScanDTO()
.setByteValue(result.getRawBytes())
.setStringValue(result.getText());
BarcodeType type = null;
switch (result.getBarcodeFormat()) {
case CODE_128:
type = BarcodeType.CODE128;
case CODE_39:
type = BarcodeType.CODE39;
case EAN_13:
type = BarcodeType.EAN13;
case EAN_8:
type = BarcodeType.EAN8;
case UPC_A:
type = BarcodeType.UPCA;
case UPC_E:
type = BarcodeType.UPCE;
default:
Logger.e("Barcode not recognized", "Barcode type " + result.getBarcodeFormat().toString() + " was not mapped in DialogCameraBarcodeReader.java");
type = null;
}
barcodeScanDTO.setType(type);
this.dismiss();
this.onComplete.run(barcodeScanDTO);
return true;
}
@Override
public void onBarcodeScanCancelled() {
}
}

View File

@ -4,7 +4,6 @@ import android.app.Dialog;
import android.content.Context;
import androidx.databinding.DataBindingUtil;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -50,7 +49,7 @@ public class DialogChooseArtFromListaArts {
mDialog.setCanceledOnTouchOutside(false);
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
UtilityDialog.setTo90PercentSize(context, mDialog);
UtilityDialog.setTo90PercentWidth(context, mDialog);
initRecyclerView(bindings, listaArts);
}

View File

@ -1,11 +1,9 @@
package it.integry.integrywmsnative.view.dialogs.choose_arts_from_lista_arts;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import androidx.databinding.DataBindingUtil;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.graphics.Color;
@ -102,7 +100,7 @@ public class DialogChooseArtsFromListaArts {
if(this.mOnAbort != null) this.mOnAbort.run();
});
UtilityDialog.setTo90PercentSize(context, mDialog);
UtilityDialog.setTo90PercentWidth(context, mDialog);
initRecyclerView(binding);

View File

@ -32,6 +32,8 @@ public class DialogInputQuantityV2ViewModel {
public void init() {
//Quando ho un barcode peso devo calcolare numCnf dal peso medio
this.currentNumCnf.set(totalNumCnfToBeTaken != null ? totalNumCnfToBeTaken : BigDecimal.ONE);
UtilityObservable.addPropertyChanged(this.currentNumCnf, this::onCurrentNumCnfChanged);

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kroegerama.kaiteki.bcode.views.BarcodeView
android:id="@+id/bcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:resultPointColor="#09E85E"
app:resultPointSize="8dp"
app:showResultPoints="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:paddingBottom="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_confirm"
style="@style/Button.PrimaryFull"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_close"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>

View File

@ -296,6 +296,7 @@
<string name="cod_alis_name">Griglia: </string>
<string name="seleziona_crea_ordine_a_pv">Seleziona / Crea ordine</string>
<string name="add">Aggiungi</string>
<string name="action_close">Chiudi</string>
<string name="close_and_export">Chiudi ed esporta ordine</string>
<string name="close_and_exit">Chiudi ordine</string>
<string name="scan_art_barcode_to_add">Scansiona il codice di un articolo per aggiungerlo all\' ordine</string>

View File

@ -302,6 +302,7 @@
<string name="cod_alis_name">Grid: </string>
<string name="seleziona_crea_ordine_a_pv">Select/Create order</string>
<string name="add">Add</string>
<string name="action_close">Close</string>
<string name="close_and_export">Close and export</string>
<string name="close_and_exit">Close order</string>
<string name="scan_art_barcode_to_add">Scan an item barcode to add it to the order</string>

1
barcode_kaiteki/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,40 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.1.1"
}
compileOptions {
kotlinOptions.freeCompilerArgs += ['-module-name', "barcode.kaiteki"]
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_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.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.google.android.material:material:1.1.0-alpha10'
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 Normal file
View File

@ -0,0 +1,21 @@
# 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

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kroegerama.kaiteki.bcode">
<uses-permission android:name="android.permission.CAMERA" />
</manifest>

View File

@ -0,0 +1,70 @@
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
}
val 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]
}
val tmp = width
width = height
height = tmp
val source = PlanarYUVLuminanceSource(rotatedData, 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"
}
}

View File

@ -0,0 +1,16 @@
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()
}

View File

@ -0,0 +1,32 @@
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)

View File

@ -0,0 +1,53 @@
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
}
}

View File

@ -0,0 +1,65 @@
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)
}

View File

@ -0,0 +1,109 @@
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)
}
}
}

View File

@ -0,0 +1,120 @@
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)
}
}
}

View File

@ -0,0 +1,84 @@
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
)
}
}
}

View File

@ -0,0 +1,197 @@
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)

View File

@ -0,0 +1,102 @@
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)
}
}

View File

@ -0,0 +1,17 @@
<?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>

View File

@ -0,0 +1,9 @@
<?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" />

View File

@ -0,0 +1,22 @@
<?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>

View File

@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.3.61'
kotlin_version = '1.3.71'
}
repositories {

View File

@ -1 +1 @@
include ':app', ':pointmobilescannerlibrary', ':dynamic_vgalimenti', ':dynamic__base', ':zebrascannerlibrary', ':honeywellscannerlibrary', ':dynamic_ime', ':dynamic_frudis', ':dynamic_saporiveri_pv', ':keyobardemulatorscannerlibrary', ':barcode_base_android_library', ':dynamic_saporiveri'
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'