From cbce52fd83cb9e2c5eeb82c639607872ad6bf9f8 Mon Sep 17 00:00:00 2001 From: GiuseppeS Date: Wed, 25 Mar 2020 19:02:14 +0100 Subject: [PATCH] Implementato completamente lo scan del barcode tramite fotocamera. --- app/build.gradle | 12 +- .../core/utility/UtilityDialog.java | 17 +- .../dialog/EditArticoloDialog.java | 5 +- .../spedizione_new/SpedizioneActivity.java | 23 +- .../view/dialogs/DialogAskAction.java | 2 +- .../dialogs/ask_cliente/DialogAskCliente.java | 10 +- .../DialogAskPositionOfLU.java | 3 +- .../dialogs/base/DialogSimpleInputHelper.java | 16 +- .../dialogs/basket_lu/DialogBasketLU.java | 11 +- .../DialogCameraBarcodeReader.java | 129 ++++++++++++ .../DialogChooseArtFromListaArts.java | 3 +- .../DialogChooseArtsFromListaArts.java | 4 +- .../DialogInputQuantityV2ViewModel.java | 2 + .../layout/dialog_camera_barcode_reader.xml | 51 +++++ app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + barcode_kaiteki/.gitignore | 1 + barcode_kaiteki/build.gradle | 40 ++++ barcode_kaiteki/proguard-rules.pro | 21 ++ barcode_kaiteki/src/main/AndroidManifest.xml | 6 + .../kaiteki/bcode/BarcodeAnalyzer.kt | 70 +++++++ .../kaiteki/bcode/BarcodeResultListener.kt | 16 ++ .../kroegerama/kaiteki/bcode/DebugUtils.kt | 32 +++ .../com/kroegerama/kaiteki/bcode/Utils.kt | 53 +++++ .../kaiteki/bcode/ui/BarcodeAlertDialog.kt | 65 ++++++ .../kaiteki/bcode/ui/BarcodeBottomSheet.kt | 109 ++++++++++ .../kaiteki/bcode/ui/BarcodeDialog.kt | 120 +++++++++++ .../kaiteki/bcode/ui/BarcodeFragment.kt | 84 ++++++++ .../kaiteki/bcode/views/BarcodeView.kt | 197 ++++++++++++++++++ .../kaiteki/bcode/views/ResultPointView.kt | 102 +++++++++ .../src/main/res/layout/barcode_view.xml | 17 ++ .../src/main/res/layout/dlg_barcode.xml | 9 + barcode_kaiteki/src/main/res/values/attrs.xml | 22 ++ build.gradle | 2 +- settings.gradle | 2 +- 35 files changed, 1184 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/it/integry/integrywmsnative/view/dialogs/camera_barcode_reader/DialogCameraBarcodeReader.java create mode 100644 app/src/main/res/layout/dialog_camera_barcode_reader.xml create mode 100644 barcode_kaiteki/.gitignore create mode 100644 barcode_kaiteki/build.gradle create mode 100644 barcode_kaiteki/proguard-rules.pro create mode 100644 barcode_kaiteki/src/main/AndroidManifest.xml create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeAnalyzer.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeResultListener.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/DebugUtils.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/Utils.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeAlertDialog.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeBottomSheet.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeDialog.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeFragment.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/BarcodeView.kt create mode 100644 barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/ResultPointView.kt create mode 100644 barcode_kaiteki/src/main/res/layout/barcode_view.xml create mode 100644 barcode_kaiteki/src/main/res/layout/dlg_barcode.xml create mode 100644 barcode_kaiteki/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 767d608d..17401dc2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/java/it/integry/integrywmsnative/core/utility/UtilityDialog.java b/app/src/main/java/it/integry/integrywmsnative/core/utility/UtilityDialog.java index 7c7bc2e4..3fb333cb 100644 --- a/app/src/main/java/it/integry/integrywmsnative/core/utility/UtilityDialog.java +++ b/app/src/main/java/it/integry/integrywmsnative/core/utility/UtilityDialog.java @@ -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); +// } + } diff --git a/app/src/main/java/it/integry/integrywmsnative/gest/pv_ordine_acquisto_edit/dialog/EditArticoloDialog.java b/app/src/main/java/it/integry/integrywmsnative/gest/pv_ordine_acquisto_edit/dialog/EditArticoloDialog.java index c16d6fc3..84dc5b19 100644 --- a/app/src/main/java/it/integry/integrywmsnative/gest/pv_ordine_acquisto_edit/dialog/EditArticoloDialog.java +++ b/app/src/main/java/it/integry/integrywmsnative/gest/pv_ordine_acquisto_edit/dialog/EditArticoloDialog.java @@ -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()); diff --git a/app/src/main/java/it/integry/integrywmsnative/gest/spedizione_new/SpedizioneActivity.java b/app/src/main/java/it/integry/integrywmsnative/gest/spedizione_new/SpedizioneActivity.java index 6799dbca..6ad09419 100644 --- a/app/src/main/java/it/integry/integrywmsnative/gest/spedizione_new/SpedizioneActivity.java +++ b/app/src/main/java/it/integry/integrywmsnative/gest/spedizione_new/SpedizioneActivity.java @@ -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 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() { diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/DialogAskAction.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/DialogAskAction.java index 247271f3..90ebbbaa 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/DialogAskAction.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/DialogAskAction.java @@ -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); diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_cliente/DialogAskCliente.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_cliente/DialogAskCliente.java index 9378c821..1f73f013 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_cliente/DialogAskCliente.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_cliente/DialogAskCliente.java @@ -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); diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_position_of_lu/DialogAskPositionOfLU.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_position_of_lu/DialogAskPositionOfLU.java index d41ff95b..522e436b 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_position_of_lu/DialogAskPositionOfLU.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/ask_position_of_lu/DialogAskPositionOfLU.java @@ -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); diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/base/DialogSimpleInputHelper.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/base/DialogSimpleInputHelper.java index 19901d73..84aa0943 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/base/DialogSimpleInputHelper.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/base/DialogSimpleInputHelper.java @@ -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; } diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/basket_lu/DialogBasketLU.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/basket_lu/DialogBasketLU.java index dbf5b69c..da230e3f 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/basket_lu/DialogBasketLU.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/basket_lu/DialogBasketLU.java @@ -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(); } diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/camera_barcode_reader/DialogCameraBarcodeReader.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/camera_barcode_reader/DialogCameraBarcodeReader.java new file mode 100644 index 00000000..eed64c59 --- /dev/null +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/camera_barcode_reader/DialogCameraBarcodeReader.java @@ -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 onComplete; + + public static DialogCameraBarcodeReader newInstance(@NonNull Context context, @NonNull final RunnableArgs onComplete) { + return new DialogCameraBarcodeReader(context, onComplete); + } + + private DialogCameraBarcodeReader(@NonNull Context context, @NonNull final RunnableArgs 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 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() { + + } +} diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_art_from_lista_arts/DialogChooseArtFromListaArts.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_art_from_lista_arts/DialogChooseArtFromListaArts.java index db915904..b1d82cea 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_art_from_lista_arts/DialogChooseArtFromListaArts.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_art_from_lista_arts/DialogChooseArtFromListaArts.java @@ -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); } diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_arts_from_lista_arts/DialogChooseArtsFromListaArts.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_arts_from_lista_arts/DialogChooseArtsFromListaArts.java index f7a97c76..36ed1ec8 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_arts_from_lista_arts/DialogChooseArtsFromListaArts.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/choose_arts_from_lista_arts/DialogChooseArtsFromListaArts.java @@ -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); diff --git a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/input_quantity_v2/DialogInputQuantityV2ViewModel.java b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/input_quantity_v2/DialogInputQuantityV2ViewModel.java index 35318f87..e1307c2a 100644 --- a/app/src/main/java/it/integry/integrywmsnative/view/dialogs/input_quantity_v2/DialogInputQuantityV2ViewModel.java +++ b/app/src/main/java/it/integry/integrywmsnative/view/dialogs/input_quantity_v2/DialogInputQuantityV2ViewModel.java @@ -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); diff --git a/app/src/main/res/layout/dialog_camera_barcode_reader.xml b/app/src/main/res/layout/dialog_camera_barcode_reader.xml new file mode 100644 index 00000000..19c3372f --- /dev/null +++ b/app/src/main/res/layout/dialog_camera_barcode_reader.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b8483a9c..bb224ae6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -296,6 +296,7 @@ Griglia: Seleziona / Crea ordine Aggiungi + Chiudi Chiudi ed esporta ordine Chiudi ordine Scansiona il codice di un articolo per aggiungerlo all\' ordine diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4eddb493..e670aa47 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -302,6 +302,7 @@ Grid: Select/Create order Add + Close Close and export Close order Scan an item barcode to add it to the order diff --git a/barcode_kaiteki/.gitignore b/barcode_kaiteki/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/barcode_kaiteki/.gitignore @@ -0,0 +1 @@ +/build diff --git a/barcode_kaiteki/build.gradle b/barcode_kaiteki/build.gradle new file mode 100644 index 00000000..57d6d637 --- /dev/null +++ b/barcode_kaiteki/build.gradle @@ -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' +} diff --git a/barcode_kaiteki/proguard-rules.pro b/barcode_kaiteki/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/barcode_kaiteki/proguard-rules.pro @@ -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 diff --git a/barcode_kaiteki/src/main/AndroidManifest.xml b/barcode_kaiteki/src/main/AndroidManifest.xml new file mode 100644 index 00000000..39b96b11 --- /dev/null +++ b/barcode_kaiteki/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeAnalyzer.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeAnalyzer.kt new file mode 100644 index 00000000..b69299a1 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeAnalyzer.kt @@ -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" + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeResultListener.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeResultListener.kt new file mode 100644 index 00000000..b75c67c6 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/BarcodeResultListener.kt @@ -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() + +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/DebugUtils.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/DebugUtils.kt new file mode 100644 index 00000000..858e739e --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/DebugUtils.kt @@ -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) \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/Utils.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/Utils.kt new file mode 100644 index 00000000..fd8b0049 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/Utils.kt @@ -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 invoke(block: () -> T) = if (System.currentTimeMillis() - lastShot > debounceTime) { + block.invoke().also { + lastShot = System.currentTimeMillis() + } + } else { + null + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeAlertDialog.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeAlertDialog.kt new file mode 100644 index 00000000..4c10ef08 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeAlertDialog.kt @@ -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 = 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(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) +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeBottomSheet.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeBottomSheet.kt new file mode 100644 index 00000000..63e6f02a --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeBottomSheet.kt @@ -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? by lazy { + arguments?.getSerializable(KEY_FORMATS) as List + } + + 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, 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 = 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) + } + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeDialog.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeDialog.kt new file mode 100644 index 00000000..a67aa3fe --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeDialog.kt @@ -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? by lazy { + arguments?.getSerializable(KEY_FORMATS) as List + } + + 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, 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 = 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) + } + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeFragment.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeFragment.kt new file mode 100644 index 00000000..a5214623 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/ui/BarcodeFragment.kt @@ -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? by lazy { + arguments?.getSerializable(KEY_FORMATS) as List + } + + 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, 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 = listOf(BarcodeFormat.QR_CODE), + barcodeInverted: Boolean = false + ) = BarcodeFragment().apply { + arguments = bundleOf( + KEY_FORMATS to formats, + KEY_INVERTED to barcodeInverted + ) + } + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/BarcodeView.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/BarcodeView.kt new file mode 100644 index 00000000..b820c7c7 --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/BarcodeView.kt @@ -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) = 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) diff --git a/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/ResultPointView.kt b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/ResultPointView.kt new file mode 100644 index 00000000..9cb98d7c --- /dev/null +++ b/barcode_kaiteki/src/main/java/com/kroegerama/kaiteki/bcode/views/ResultPointView.kt @@ -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) + } +} \ No newline at end of file diff --git a/barcode_kaiteki/src/main/res/layout/barcode_view.xml b/barcode_kaiteki/src/main/res/layout/barcode_view.xml new file mode 100644 index 00000000..d22acedd --- /dev/null +++ b/barcode_kaiteki/src/main/res/layout/barcode_view.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/barcode_kaiteki/src/main/res/layout/dlg_barcode.xml b/barcode_kaiteki/src/main/res/layout/dlg_barcode.xml new file mode 100644 index 00000000..bcae9b0d --- /dev/null +++ b/barcode_kaiteki/src/main/res/layout/dlg_barcode.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/barcode_kaiteki/src/main/res/values/attrs.xml b/barcode_kaiteki/src/main/res/values/attrs.xml new file mode 100644 index 00000000..7d85a540 --- /dev/null +++ b/barcode_kaiteki/src/main/res/values/attrs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c29e3704..35693719 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - kotlin_version = '1.3.61' + kotlin_version = '1.3.71' } repositories { diff --git a/settings.gradle b/settings.gradle index bf3f1d7e..e3a5ec53 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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'