diff --git a/README.md b/README.md index e30a62a49..1637a121b 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ app/build.gradle: ``` dependencies { - implementation 'net.gini:gini-vision-lib:4.0.1' + implementation 'net.gini:gini-vision-lib:4.1.0' } ``` diff --git a/ginivision-accounting-network/README.md b/ginivision-accounting-network/README.md index e4f056a27..ae6512593 100644 --- a/ginivision-accounting-network/README.md +++ b/ginivision-accounting-network/README.md @@ -50,8 +50,8 @@ app/build.gradle: ``` dependencies { - implementation 'net.gini:gini-vision-lib:4.0.1' - implementation 'net.gini:gini-vision-accounting-network-lib:4.0.1' + implementation 'net.gini:gini-vision-lib:4.1.0' + implementation 'net.gini:gini-vision-accounting-network-lib:4.1.0' } ``` diff --git a/ginivision-network/README.md b/ginivision-network/README.md index ad4c2711b..8ea1cccb4 100644 --- a/ginivision-network/README.md +++ b/ginivision-network/README.md @@ -48,8 +48,8 @@ app/build.gradle: ``` dependencies { - implementation 'net.gini:gini-vision-lib:4.0.1' - implementation 'net.gini:gini-vision-network-lib:4.0.1' + implementation 'net.gini:gini-vision-lib:4.1.0' + implementation 'net.gini:gini-vision-network-lib:4.1.0' } ``` diff --git a/ginivision/src/androidTest/java/net/gini/android/vision/camera/CameraScreenTest.java b/ginivision/src/androidTest/java/net/gini/android/vision/camera/CameraScreenTest.java index cf79f8059..7b4fa386d 100644 --- a/ginivision/src/androidTest/java/net/gini/android/vision/camera/CameraScreenTest.java +++ b/ginivision/src/androidTest/java/net/gini/android/vision/camera/CameraScreenTest.java @@ -57,6 +57,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.io.IOException; @@ -665,7 +666,8 @@ public void should_hideAndShowPaymentDataDetectedPopup_whenNewPaymentData_wasDet cameraActivityFake.getCameraFragmentImplFake(); Thread.sleep(CameraFragmentImpl.DEFAULT_ANIMATION_DURATION + 100); Mockito.verify(cameraFragmentImplFake, times(2)) - .showQRCodeDetectedPopup(anyLong()); + .mPaymentQRCodePopup.show(ArgumentMatchers.any(), + anyLong()); } @Test diff --git a/ginivision/src/doc/requirements.txt b/ginivision/src/doc/requirements.txt index 0656a27d1..efa88f9b2 100644 --- a/ginivision/src/doc/requirements.txt +++ b/ginivision/src/doc/requirements.txt @@ -1,4 +1,4 @@ -Jinja2==2.10.1 +Jinja2==2.11.3 MarkupSafe==0.23 Pygments==2.0.1 Sphinx==1.2.3 diff --git a/ginivision/src/doc/source/customization-guide.rst b/ginivision/src/doc/source/customization-guide.rst index 92ca0e997..20baace29 100644 --- a/ginivision/src/doc/source/customization-guide.rst +++ b/ginivision/src/doc/source/customization-guide.rst @@ -430,9 +430,12 @@ All Action Bar customizations except the title are global to all Activities. .. _camera-8: -8. Document Import Hint +8. Hints ^^^^ +8.1 Document Import Hint +~~~~ + - **Background Color** Via the color resource named ``gv_document_import_hint_background``. @@ -458,6 +461,34 @@ All Action Bar customizations except the title are global to all Activities. parent style ``Root.GiniVisionTheme.Camera.DocumentImportHint.TextStyle``) and setting an item named ``gvCustomFont`` with the path to the font file in your assets folder. +8.2 QR Code Scanner Hint +~~~~ + +- **Background Color** + + Via the color resource named ``gv_document_import_hint_background``. + +- **Close Icon Color** + + Via the color resource name ``gv_hint_close``. + +- **Message** + + - **Text** + + Via the string resource named ``gv_qr_code_scanner_hint_text``. + + - **Text Style** + + Via overriding the style named ``GiniVisionTheme.Camera.DocumentImportHint.TextStyle`` (with + parent style ``Root.GiniVisionTheme.Camera.DocumentImportHint.TextStyle``). + + - **Font** + + Via overriding the style named ``GiniVisionTheme.Camera.DocumentImportHint.TextStyle`` (with + parent style ``Root.GiniVisionTheme.Camera.DocumentImportHint.TextStyle``) and setting an + item named ``gvCustomFont`` with the path to the font file in your assets folder. + :ref:`Back to screenshots. ` .. _camera-9: @@ -513,31 +544,66 @@ All Action Bar customizations except the title are global to all Activities. - **Background Color** - Via the color resource named ``gv_qrcode_detected_popup_background``. + - **Payable QRCode** + + Via the color resource named ``gv_qrcode_detected_popup_background``. + + - **Unsupported QRCode** + + Via the color resource named ``gv_unsupported_qrcode_detected_popup_background``. - **Message** - **Text** - Via the string resources named ``gv_qrcode_detected_popup_message_1`` and - ``gv_qrcode_detected_popup_message_2``. + - **Payable QRCode** + + Via the string resources named ``gv_qrcode_detected_popup_message_1`` and + ``gv_qrcode_detected_popup_message_2``. + + - **Unsupported QRCode** + + Via the string resources named ``gv_unsupported_qrcode_detected_popup_message_1`` and + ``gv_unsupported_qrcode_detected_popup_message_2``. - **Text Style** - Via overriding the styles named - ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle`` (with parent style - ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle``) and - ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle`` (with parent style - ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle``). + - **Payable QRCode** + + Via overriding the styles named + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle``) and + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle``). + + - **Unsupported QRCode** + + Via overriding the styles named + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage1.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage1.TextStyle``) and + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage2.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage2.TextStyle``). + - **Font** - Via overriding the styles named - ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle`` (with parent style - ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle``) and - ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle`` (with parent style - ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle``). and setting an - item named ``gvCustomFont`` with the path to the font file in your assets folder. + - **Payable QRCode** + + Via overriding the styles named + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message1.TextStyle``) and + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.Message2.TextStyle``). and setting an + item named ``gvCustomFont`` with the path to the font file in your assets folder. + + - **Unsupported QRCode** + + Via overriding the styles named + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage1.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage1.TextStyle``) and + ``GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage2.TextStyle`` (with parent style + ``Root.GiniVisionTheme.Camera.QRCodeDetectedPopup.UnsupportedMessage2.TextStyle``). and setting an + item named ``gvCustomFont`` with the path to the font file in your assets folder. :ref:`Back to screenshots. ` diff --git a/ginivision/src/main/java/net/gini/android/vision/camera/CameraFragmentImpl.java b/ginivision/src/main/java/net/gini/android/vision/camera/CameraFragmentImpl.java index e65dde156..1886d69c8 100644 --- a/ginivision/src/main/java/net/gini/android/vision/camera/CameraFragmentImpl.java +++ b/ginivision/src/main/java/net/gini/android/vision/camera/CameraFragmentImpl.java @@ -1,21 +1,5 @@ package net.gini.android.vision.camera; -import static android.app.Activity.RESULT_CANCELED; -import static android.app.Activity.RESULT_OK; - -import static net.gini.android.vision.camera.Util.cameraExceptionToGiniVisionError; -import static net.gini.android.vision.document.ImageDocument.ImportMethod; -import static net.gini.android.vision.internal.camera.view.FlashButtonHelper.getFlashButtonPosition; -import static net.gini.android.vision.internal.network.NetworkRequestsManager.isCancellation; -import static net.gini.android.vision.internal.qrcode.EPSPaymentParser.EXTRACTION_ENTITY_NAME; -import static net.gini.android.vision.internal.util.ActivityHelper.forcePortraitOrientationOnPhones; -import static net.gini.android.vision.internal.util.AndroidHelper.isMarshmallowOrLater; -import static net.gini.android.vision.internal.util.ContextHelper.isTablet; -import static net.gini.android.vision.internal.util.FeatureConfiguration.getDocumentImportEnabledFileTypes; -import static net.gini.android.vision.internal.util.FeatureConfiguration.isMultiPageEnabled; -import static net.gini.android.vision.internal.util.FeatureConfiguration.isQRCodeScanningEnabled; -import static net.gini.android.vision.tracking.EventTrackingHelper.trackCameraScreenEvent; - import android.Manifest; import android.app.Activity; import android.content.Context; @@ -39,6 +23,17 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.VisibleForTesting; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorCompat; +import androidx.core.view.ViewPropertyAnimatorListenerAdapter; +import androidx.transition.Transition; +import androidx.transition.TransitionListenerAdapter; + import net.gini.android.vision.AsyncCallback; import net.gini.android.vision.Document; import net.gini.android.vision.DocumentImportEnabledFileTypes; @@ -61,6 +56,8 @@ import net.gini.android.vision.internal.camera.photo.PhotoEdit; import net.gini.android.vision.internal.camera.view.CameraPreviewSurface; import net.gini.android.vision.internal.camera.view.FlashButtonHelper.FlashButtonPosition; +import net.gini.android.vision.internal.camera.view.HintPopup; +import net.gini.android.vision.internal.camera.view.QRCodePopup; import net.gini.android.vision.internal.fileimport.FileChooserActivity; import net.gini.android.vision.internal.network.AnalysisNetworkRequestResult; import net.gini.android.vision.internal.network.NetworkRequestResult; @@ -94,18 +91,25 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.ViewPropertyAnimatorCompat; -import androidx.core.view.ViewPropertyAnimatorListener; -import androidx.core.view.ViewPropertyAnimatorListenerAdapter; -import androidx.transition.Transition; -import androidx.transition.TransitionListenerAdapter; import jersey.repackaged.jsr166e.CompletableFuture; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; + +import static android.app.Activity.RESULT_CANCELED; +import static android.app.Activity.RESULT_OK; +import static net.gini.android.vision.camera.Util.cameraExceptionToGiniVisionError; +import static net.gini.android.vision.document.ImageDocument.ImportMethod; +import static net.gini.android.vision.internal.camera.view.FlashButtonHelper.getFlashButtonPosition; +import static net.gini.android.vision.internal.network.NetworkRequestsManager.isCancellation; +import static net.gini.android.vision.internal.qrcode.EPSPaymentParser.EXTRACTION_ENTITY_NAME; +import static net.gini.android.vision.internal.util.ActivityHelper.forcePortraitOrientationOnPhones; +import static net.gini.android.vision.internal.util.AndroidHelper.isMarshmallowOrLater; +import static net.gini.android.vision.internal.util.ContextHelper.isTablet; +import static net.gini.android.vision.internal.util.FeatureConfiguration.getDocumentImportEnabledFileTypes; +import static net.gini.android.vision.internal.util.FeatureConfiguration.isMultiPageEnabled; +import static net.gini.android.vision.internal.util.FeatureConfiguration.isQRCodeScanningEnabled; +import static net.gini.android.vision.tracking.EventTrackingHelper.trackCameraScreenEvent; class CameraFragmentImpl implements CameraFragmentInterface, PaymentQRCodeReader.Listener { @@ -149,13 +153,17 @@ public void onExtractionsAvailable( }; private static final int REQ_CODE_CHOOSE_FILE = 1; - private static final String SHOW_HINT_POP_UP = "SHOW_HINT_POP_UP"; + private static final String SHOW_UPLOAD_HINT_POP_UP = "SHOW_HINT_POP_UP"; + private static final String SHOW_QRCODE_SCANNER_HINT_POP_UP = "SHOW_QR_CODE_SCANNER_HINT_POP_UP"; private static final String IN_MULTI_PAGE_STATE_KEY = "IN_MULTI_PAGE_STATE_KEY"; private static final String IS_FLASH_ENABLED_KEY = "IS_FLASH_ENABLED_KEY"; private final FragmentImplCallback mFragment; private final GiniVisionFeatureConfiguration mGiniVisionFeatureConfiguration; - private HideQRCodeDetectedRunnable mHideQRCodeDetectedPopupRunnable; + + @VisibleForTesting + QRCodePopup mPaymentQRCodePopup; + private QRCodePopup mUnsupportedQRCodePopup; private View mImageCorners; private ImageStack mImageStack; @@ -177,17 +185,20 @@ public void onExtractionsAvailable( private LinearLayout mLayoutNoPermission; private ImageButton mButtonImportDocument; private View mQRCodeDetectedPopupContainer; - private PaymentQRCodeData mPaymentQRCodeData; + private View mUnsupportedQRCodeDetectedPopupContainer; private View mUploadHintCloseButton; private View mUploadHintContainer; private View mUploadHintContainerArrow; + private View mQRCodeScannerHintCloseButton; + private View mQRCodeScannerHintContainer; + private View mQRCodeScannerHintContainerArrow; private View mCameraPreviewShade; private View mActivityIndicatorBackground; private ProgressBar mActivityIndicator; - private ViewPropertyAnimatorCompat mUploadHintContainerArrowAnimation; private ViewPropertyAnimatorCompat mCameraPreviewShadeAnimation; - private ViewPropertyAnimatorCompat mUploadHintContainerAnimation; - private ViewPropertyAnimatorCompat mQRCodeDetectedPopupAnimation; + + private HintPopup mUploadHintPopup; + private HintPopup mQRCodeScannerHintPopup; private ViewStubSafeInflater mViewStubInflater; @@ -214,42 +225,43 @@ public void onExtractionsAvailable( @Override public void onPaymentQRCodeDataAvailable(@NonNull final PaymentQRCodeData paymentQRCodeData) { + handleQRCodeDetected(paymentQRCodeData, paymentQRCodeData.getUnparsedContent()); + } + + @Override + public void onNonPaymentQRCodeDetected(@NonNull String qrCodeContent) { + handleQRCodeDetected(null, qrCodeContent); + } + + private void handleQRCodeDetected(@Nullable final PaymentQRCodeData paymentQRCodeData, + @NonNull final String qrCodeContent) { if (mUploadHintContainer.getVisibility() == View.VISIBLE || mInterfaceHidden || mActivityIndicator.getVisibility() == View.VISIBLE) { - hideQRCodeDetectedPopup(null); - mPaymentQRCodeData = null; // NOPMD - return; - } - - final View view = mFragment.getView(); - if (view == null) { + mPaymentQRCodePopup.hide(); + mUnsupportedQRCodePopup.hide(); return; } - if (mPaymentQRCodeData == null - || mQRCodeDetectedPopupContainer.getVisibility() == View.GONE) { - showQRCodeDetectedPopup(0); - view.removeCallbacks(mHideQRCodeDetectedPopupRunnable); - view.postDelayed(mHideQRCodeDetectedPopupRunnable, - getHideQRCodeDetectedPopupDelayMs()); + if (paymentQRCodeData == null) { + final boolean showWithDelay = mPaymentQRCodePopup.isShown(); + mPaymentQRCodePopup.hide(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mUnsupportedQRCodePopup.show(qrCodeContent, + showWithDelay ? getDifferentQRCodeDetectedPopupDelayMs() : 0); + } + }); } else { - if (mPaymentQRCodeData.equals(paymentQRCodeData)) { - view.removeCallbacks(mHideQRCodeDetectedPopupRunnable); - view.postDelayed(mHideQRCodeDetectedPopupRunnable, - getHideQRCodeDetectedPopupDelayMs()); - } else { - view.removeCallbacks(mHideQRCodeDetectedPopupRunnable); - hideQRCodeDetectedPopup(new ViewPropertyAnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final View view) { - showQRCodeDetectedPopup( - getDifferentQRCodeDetectedPopupDelayMs()); - } - }); - } + final boolean showWithDelay = mUnsupportedQRCodePopup.isShown(); + mUnsupportedQRCodePopup.hide(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mPaymentQRCodePopup.show(paymentQRCodeData, + showWithDelay ? getDifferentQRCodeDetectedPopupDelayMs() : 0); + } + }); } - mPaymentQRCodeData = paymentQRCodeData; } @VisibleForTesting @@ -262,65 +274,6 @@ long getDifferentQRCodeDetectedPopupDelayMs() { return DIFFERENT_QRCODE_DETECTED_POPUP_DELAY_MS; } - private class HideQRCodeDetectedRunnable implements Runnable { - - @Override - public void run() { - hideQRCodeDetectedPopup(null); - mPaymentQRCodeData = null; // NOPMD - } - } - - @VisibleForTesting - void showQRCodeDetectedPopup(final long startDelay) { - if (mQRCodeDetectedPopupContainer.getAlpha() != 0) { - return; - } - clearQRCodeDetectedPopUpAnimation(); - mQRCodeDetectedPopupContainer.setVisibility(View.VISIBLE); - mQRCodeDetectedPopupAnimation = ViewCompat.animate(mQRCodeDetectedPopupContainer) - .alpha(1.0f) - .setStartDelay(startDelay) - .setDuration(DEFAULT_ANIMATION_DURATION); - mQRCodeDetectedPopupAnimation.start(); - } - - private void hideQRCodeDetectedPopup( - @Nullable final ViewPropertyAnimatorListener animatorListener) { - if (mQRCodeDetectedPopupContainer.getAlpha() != 1) { - if (animatorListener != null) { - animatorListener.onAnimationEnd(mQRCodeDetectedPopupContainer); - } - return; - } - clearQRCodeDetectedPopUpAnimation(); - mQRCodeDetectedPopupAnimation = ViewCompat.animate(mQRCodeDetectedPopupContainer) - .alpha(0.0f) - .setDuration(DEFAULT_ANIMATION_DURATION) - .setListener(new ViewPropertyAnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final View view) { - mQRCodeDetectedPopupContainer.setVisibility(View.GONE); - if (animatorListener != null) { - animatorListener.onAnimationEnd(view); - } - } - }); - mQRCodeDetectedPopupAnimation.start(); - } - - private void clearQRCodeDetectedPopUpAnimation() { - if (mQRCodeDetectedPopupAnimation != null) { - mQRCodeDetectedPopupAnimation.cancel(); - mQRCodeDetectedPopupContainer.clearAnimation(); - mQRCodeDetectedPopupAnimation.setListener(null); - } - final View view = mFragment.getView(); - if (view != null) { - view.removeCallbacks(mHideQRCodeDetectedPopupRunnable); - } - } - @Override public void setListener(@NonNull final CameraFragmentListener listener) { mListener = listener; @@ -355,9 +308,52 @@ View onCreateView(final LayoutInflater inflater, final ViewGroup container, bindViews(view); setInputHandlers(); setSurfaceViewCallback(); + createPopups(); return view; } + private void createPopups() { + mPaymentQRCodePopup = + new QRCodePopup<>(mFragment, mQRCodeDetectedPopupContainer, + DEFAULT_ANIMATION_DURATION, getHideQRCodeDetectedPopupDelayMs(), + getDifferentQRCodeDetectedPopupDelayMs(), + new Function1() { + @Override + public Unit invoke(@Nullable PaymentQRCodeData paymentQRCodeData) { + if (paymentQRCodeData == null) { + return null; + } + handlePaymentQRCodeData(paymentQRCodeData); + return null; + } + }); + + mUnsupportedQRCodePopup = + new QRCodePopup<>(mFragment, mUnsupportedQRCodeDetectedPopupContainer, + DEFAULT_ANIMATION_DURATION, getHideQRCodeDetectedPopupDelayMs(), + getDifferentQRCodeDetectedPopupDelayMs()); + + mUploadHintPopup = new HintPopup(mUploadHintContainer, mUploadHintContainerArrow, + mUploadHintCloseButton, DEFAULT_ANIMATION_DURATION, + new Function0() { + @Override + public Unit invoke() { + closeUploadHintPopUp(); + return null; + } + }); + + mQRCodeScannerHintPopup = new HintPopup(mQRCodeScannerHintContainer, + mQRCodeScannerHintContainerArrow, mQRCodeScannerHintCloseButton, + DEFAULT_ANIMATION_DURATION, new Function0() { + @Override + public Unit invoke() { + closeQRCodeScannerHintPopUp(); + return null; + } + }); + } + public void onStart() { final Activity activity = mFragment.getActivity(); if (activity == null) { @@ -368,7 +364,6 @@ public void onStart() { initViews(); initCameraController(activity); if (isQRCodeScanningEnabled(mGiniVisionFeatureConfiguration)) { - mHideQRCodeDetectedPopupRunnable = new HideQRCodeDetectedRunnable(); initQRCodeReader(activity); } @@ -394,7 +389,7 @@ public Object apply(final Void aVoid, final Throwable throwable) { mCameraPreview.setPreviewSize(previewSize); startPreview(surfaceHolder); enableTapToFocus(); - showUploadHintPopUpOnFirstExecution(); + showHintPopUpsOnFirstExecution(); initFlashButton(); } else { handleError(GiniVisionError.ErrorCode.CAMERA_NO_PREVIEW, @@ -487,29 +482,52 @@ PaymentQRCodeReader getPaymentQRCodeReader() { private void showUploadHintPopUpOnFirstExecution() { - if (shouldShowHintPopUp()) { + if (mInterfaceHidden) { + return; + } + if (shouldShowUploadHintPopUp()) { showUploadHintPopUp(); } } + private void showQrcodeScannerHintPopUpOnFirstExecution() { + if (mInterfaceHidden) { + return; + } + if (shouldShowQRCodeScannerHintPopup()) { + showQRCodeScannerHintPopUp(); + } + } + + private void showHintPopUpsOnFirstExecution() { + if (mInterfaceHidden) { + return; + } + if (shouldShowUploadHintPopUp()) { + showUploadHintPopUp(); + } else if (shouldShowQRCodeScannerHintPopup()) { + showQRCodeScannerHintPopUp(); + } + } + @VisibleForTesting void showUploadHintPopUp() { disableCameraTriggerButtonAnimated(0.3f); - mUploadHintContainer.setVisibility(View.VISIBLE); - mUploadHintContainerArrow.setVisibility(View.VISIBLE); + disableFlashButtonAnimated(0.3f); + showHintPopup(mUploadHintPopup); + } + + private void showQRCodeScannerHintPopUp() { + disableImportButtonAnimated(0.3f); + disableFlashButtonAnimated(0.3f); + showHintPopup(mQRCodeScannerHintPopup); + } + + private void showHintPopup(@NonNull final HintPopup hintPopup) { + clearHintPopupRelatedAnimations(); + hintPopup.show(); mCameraPreviewShade.setVisibility(View.VISIBLE); mCameraPreviewShade.setClickable(true); - clearUploadHintPopUpAnimations(); - mUploadHintContainerAnimation = ViewCompat.animate( - mUploadHintContainer) - .alpha(1) - .setDuration(DEFAULT_ANIMATION_DURATION); - mUploadHintContainerAnimation.start(); - mUploadHintContainerArrowAnimation = ViewCompat.animate( - mUploadHintContainerArrow) - .alpha(1) - .setDuration(DEFAULT_ANIMATION_DURATION); - mUploadHintContainerArrowAnimation.start(); mCameraPreviewShadeAnimation = ViewCompat.animate( mCameraPreviewShade) .alpha(1) @@ -517,17 +535,7 @@ void showUploadHintPopUp() { mCameraPreviewShadeAnimation.start(); } - private void clearUploadHintPopUpAnimations() { - if (mUploadHintContainerAnimation != null) { - mUploadHintContainerAnimation.cancel(); - mUploadHintContainer.clearAnimation(); - mUploadHintContainerAnimation.setListener(null); - } - if (mUploadHintContainerArrowAnimation != null) { - mUploadHintContainerArrowAnimation.cancel(); - mUploadHintContainerArrow.clearAnimation(); - mUploadHintContainerArrowAnimation.setListener(null); - } + private void clearHintPopupRelatedAnimations() { if (mCameraPreviewShadeAnimation != null) { mCameraPreviewShadeAnimation.cancel(); mCameraPreviewShade.clearAnimation(); @@ -535,7 +543,7 @@ private void clearUploadHintPopUpAnimations() { } } - private boolean shouldShowHintPopUp() { + private boolean shouldShowUploadHintPopUp() { final Activity activity = mFragment.getActivity(); if (activity == null) { return false; @@ -547,7 +555,7 @@ private boolean shouldShowHintPopUp() { if (context != null) { final SharedPreferences gvSharedPrefs = context.getSharedPreferences(GV_SHARED_PREFS, Context.MODE_PRIVATE); - return gvSharedPrefs.getBoolean(SHOW_HINT_POP_UP, true); + return gvSharedPrefs.getBoolean(SHOW_UPLOAD_HINT_POP_UP, true); } return false; } @@ -658,8 +666,14 @@ void onSaveInstanceState(@NonNull final Bundle outState) { void onStop() { closeCamera(); - clearUploadHintPopUpAnimations(); - clearQRCodeDetectedPopUpAnimation(); + clearHintPopupRelatedAnimations(); + mUploadHintPopup.hide(null); + if (mPaymentQRCodePopup != null) { + mPaymentQRCodePopup.hide(); + } + if (mUnsupportedQRCodePopup != null) { + mUnsupportedQRCodePopup.hide(); + } } void onDestroy() { @@ -765,13 +779,17 @@ private void bindViews(final View view) { mUploadHintContainer = view.findViewById(R.id.gv_document_import_hint_container); mUploadHintContainerArrow = view.findViewById(R.id.gv_document_import_hint_container_arrow); mUploadHintCloseButton = view.findViewById(R.id.gv_document_import_hint_close_button); + mQRCodeScannerHintContainer = view.findViewById(R.id.gv_qr_code_scanner_hint_container); + mQRCodeScannerHintContainerArrow = view.findViewById(R.id.gv_qr_code_scanner_hint_container_arrow); + mQRCodeScannerHintCloseButton = view.findViewById(R.id.gv_qr_code_scanner_hint_close_button); mCameraPreviewShade = view.findViewById(R.id.gv_camera_preview_shade); mActivityIndicatorBackground = view.findViewById(R.id.gv_activity_indicator_background); mActivityIndicator = view.findViewById(R.id.gv_activity_indicator); mQRCodeDetectedPopupContainer = view.findViewById( - R.id.gv_qrcode_detected_popup_container); + mUnsupportedQRCodeDetectedPopupContainer = view.findViewById( + R.id.gv_unsupported_qrcode_detected_popup_container); mImageStack = view.findViewById(R.id.gv_image_stack); } @@ -823,10 +841,21 @@ private boolean isDocumentImportEnabled(@NonNull final Activity activity) { } private void setInputHandlers() { + mCameraPreviewShade.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + closeUploadHintPopUp(); + closeQRCodeScannerHintPopUp(); + } + }); mButtonCameraTrigger.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { - onCameraTriggerClicked(); + if (mQRCodeScannerHintPopup.isShown()) { + closeQRCodeScannerHintPopUp(); + } else { + onCameraTriggerClicked(); + } } }); mButtonCameraFlash.setOnClickListener(new View.OnClickListener() { @@ -839,23 +868,11 @@ public void onClick(final View v) { mImportButtonContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { + mUploadHintPopup.setIsLastPopup(true); closeUploadHintPopUp(); showFileChooser(); } }); - mUploadHintCloseButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - closeUploadHintPopUp(); - } - }); - mQRCodeDetectedPopupContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - hideQRCodeDetectedPopup(null); - handlePaymentQRCodeData(); - } - }); mImageStack.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { @@ -897,34 +914,30 @@ public void run() { }); } - private void handlePaymentQRCodeData() { - if (mPaymentQRCodeData == null) { - return; - } - switch (mPaymentQRCodeData.getFormat()) { + private void handlePaymentQRCodeData(@NonNull final PaymentQRCodeData paymentQRCodeData) { + switch (paymentQRCodeData.getFormat()) { case EPC069_12: case BEZAHL_CODE: mQRCodeDocument = QRCodeDocument.fromPaymentQRCodeData( - mPaymentQRCodeData); + paymentQRCodeData); analyzeQRCode(mQRCodeDocument); break; case EPS_PAYMENT: - handleEPSPaymentQRCode(); + handleEPSPaymentQRCode(paymentQRCodeData); break; default: - LOG.error("Unknown payment QR Code format: {}", mPaymentQRCodeData); + LOG.error("Unknown payment QR Code format: {}", paymentQRCodeData); break; } - mPaymentQRCodeData = null; // NOPMD } - private void handleEPSPaymentQRCode() { + private void handleEPSPaymentQRCode(@NonNull final PaymentQRCodeData paymentQRCodeData) { final GiniVisionExtraction extraction = new GiniVisionExtraction( - mPaymentQRCodeData.getUnparsedContent(), EXTRACTION_ENTITY_NAME, + paymentQRCodeData.getUnparsedContent(), EXTRACTION_ENTITY_NAME, null); final GiniVisionSpecificExtraction specificExtraction = new GiniVisionSpecificExtraction( EXTRACTION_ENTITY_NAME, - mPaymentQRCodeData.getUnparsedContent(), + paymentQRCodeData.getUnparsedContent(), EXTRACTION_ENTITY_NAME, null, Collections.singletonList(extraction) @@ -1029,51 +1042,101 @@ private void handleAnalysisError() { } private void closeUploadHintPopUp() { - hideUploadHintPopUp(new ViewPropertyAnimatorListenerAdapter() { + if (!mUploadHintPopup.isShown()) { + return; + } + hideUploadHintPopup(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationEnd(final View view) { final Context context = view.getContext(); - savePopUpShown(context); + saveUploadHintPopUpShown(context); + if (shouldShowQRCodeScannerHintPopup()) { + showQRCodeScannerHintPopUp(); + } } }); } - private void hideUploadHintPopUp(@Nullable final ViewPropertyAnimatorListenerAdapter - animatorListener) { + private void hideUploadHintPopup(@Nullable final ViewPropertyAnimatorListenerAdapter + animatorListener) { if (!mInterfaceHidden) { enableCameraTriggerButtonAnimated(); + enableFlashButtonAnimated(); } - clearUploadHintPopUpAnimations(); - mUploadHintContainerAnimation = ViewCompat.animate(mUploadHintContainer) - .alpha(0) - .setDuration(DEFAULT_ANIMATION_DURATION) - .setListener(new ViewPropertyAnimatorListenerAdapter() { - @Override - public void onAnimationEnd(final View view) { - mUploadHintContainerArrow.setVisibility(View.GONE); - mUploadHintContainer.setVisibility(View.GONE); - mCameraPreviewShade.setVisibility(View.GONE); - mCameraPreviewShade.setClickable(false); - if (animatorListener != null) { - animatorListener.onAnimationEnd(view); - } - } - }); - mUploadHintContainerAnimation.start(); - mUploadHintContainerArrowAnimation = ViewCompat.animate(mUploadHintContainerArrow) - .alpha(0) - .setDuration(DEFAULT_ANIMATION_DURATION); - mUploadHintContainerArrowAnimation.start(); + hideHintPopup(mUploadHintPopup, animatorListener); + } + + private void hideHintPopup(@NonNull final HintPopup hintPopup, + @Nullable final ViewPropertyAnimatorListenerAdapter + animatorListener) { + clearHintPopupRelatedAnimations(); + hintPopup.hide(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + mCameraPreviewShade.setVisibility(View.GONE); + mCameraPreviewShade.setClickable(false); + if (animatorListener != null) { + animatorListener.onAnimationEnd(view); + } + } + }); mCameraPreviewShadeAnimation = ViewCompat.animate(mCameraPreviewShade) .alpha(0) .setDuration(DEFAULT_ANIMATION_DURATION); mCameraPreviewShadeAnimation.start(); } - private void savePopUpShown(final Context context) { + private void closeQRCodeScannerHintPopUp() { + if (!mQRCodeScannerHintPopup.isShown()) { + return; + } + hideQRCodeScannerHintPopup(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(final View view) { + final Context context = view.getContext(); + saveQRCodeScannerHintPopUpShown(context); + } + }); + } + + private boolean shouldShowQRCodeScannerHintPopup() { + final Activity activity = mFragment.getActivity(); + if (activity == null) { + return false; + } + if (!isQRCodeScanningEnabled(mGiniVisionFeatureConfiguration) + || mInterfaceHidden + || mUploadHintPopup.isLastPopup()) { + return false; + } + final Context context = mFragment.getActivity(); + if (context != null) { + final SharedPreferences gvSharedPrefs = context.getSharedPreferences(GV_SHARED_PREFS, + Context.MODE_PRIVATE); + return gvSharedPrefs.getBoolean(SHOW_QRCODE_SCANNER_HINT_POP_UP, true); + } + return false; + } + + private void hideQRCodeScannerHintPopup(@Nullable final ViewPropertyAnimatorListenerAdapter + animatorListener) { + if (!mInterfaceHidden) { + enableFlashButtonAnimated(); + enableImportButtonAnimated(); + } + hideHintPopup(mQRCodeScannerHintPopup, animatorListener); + } + + private void saveUploadHintPopUpShown(final Context context) { final SharedPreferences gvSharedPrefs = context.getSharedPreferences(GV_SHARED_PREFS, Context.MODE_PRIVATE); - gvSharedPrefs.edit().putBoolean(SHOW_HINT_POP_UP, false).apply(); + gvSharedPrefs.edit().putBoolean(SHOW_UPLOAD_HINT_POP_UP, false).apply(); + } + + private void saveQRCodeScannerHintPopUpShown(final Context context) { + final SharedPreferences gvSharedPrefs = context.getSharedPreferences(GV_SHARED_PREFS, + Context.MODE_PRIVATE); + gvSharedPrefs.edit().putBoolean(SHOW_QRCODE_SCANNER_HINT_POP_UP, false).apply(); } private void showFileChooser() { @@ -1407,22 +1470,26 @@ public void onCancelled() { private void enableInteraction() { if (mCameraPreview == null || mButtonImportDocument == null + || mImportButtonContainer == null || mButtonCameraTrigger == null) { return; } mCameraPreview.setEnabled(true); mButtonImportDocument.setEnabled(true); + mImportButtonContainer.setEnabled(true); mButtonCameraTrigger.setEnabled(true); } private void disableInteraction() { if (mCameraPreview == null || mButtonImportDocument == null + || mImportButtonContainer == null || mButtonCameraTrigger == null) { return; } mCameraPreview.setEnabled(false); mButtonImportDocument.setEnabled(false); + mImportButtonContainer.setEnabled(false); mButtonCameraTrigger.setEnabled(false); } @@ -1679,6 +1746,32 @@ private void enableCameraTriggerButtonAnimated() { mButtonCameraTrigger.setEnabled(true); } + private void disableFlashButtonAnimated(final float alpha) { + mButtonCameraFlash.clearAnimation(); + mButtonCameraFlash.animate().alpha(alpha).start(); + mButtonCameraFlash.setEnabled(false); + } + + private void enableFlashButtonAnimated() { + mButtonCameraFlash.clearAnimation(); + mButtonCameraFlash.animate().alpha(1.0f).start(); + mButtonCameraFlash.setEnabled(true); + } + + private void enableImportButtonAnimated() { + mImportButtonContainer.clearAnimation(); + mImportButtonContainer.animate().alpha(1.0f).start(); + mButtonImportDocument.setEnabled(true); + mImportButtonContainer.setEnabled(true); + } + + private void disableImportButtonAnimated(final float alpha) { + mImportButtonContainer.clearAnimation(); + mImportButtonContainer.animate().alpha(alpha).start(); + mButtonImportDocument.setEnabled(false); + mImportButtonContainer.setEnabled(false); + } + @Override public void showInterface() { if (!mInterfaceHidden || isNoPermissionViewVisible()) { @@ -1692,11 +1785,13 @@ private void showInterfaceAnimated() { showCameraTriggerButtonAnimated(); showDocumentCornerGuidesAnimated(); showImageStackAnimated(); + showFlashButtonAnimated(); if (mImportDocumentButtonEnabled) { showUploadHintPopUpOnFirstExecution(); showImportDocumentButtonAnimated(); + } else { + showQrcodeScannerHintPopUpOnFirstExecution(); } - showFlashButtonAnimated(); } private void showImageStackAnimated() { @@ -1706,6 +1801,7 @@ private void showImageStackAnimated() { private void showImportDocumentButtonAnimated() { mImportButtonContainer.animate().alpha(1.0f); mButtonImportDocument.setEnabled(true); + mImportButtonContainer.setEnabled(true); } private void showFlashButtonAnimated() { @@ -1727,9 +1823,10 @@ private void hideInterfaceAnimated() { hideDocumentCornerGuidesAnimated(); hideImageStackAnimated(); if (mImportDocumentButtonEnabled) { - hideUploadHintPopUp(null); + hideUploadHintPopup(null); hideImportDocumentButtonAnimated(); } + hideQRCodeScannerHintPopup(null); hideFlashButtonAnimated(); } @@ -1740,6 +1837,7 @@ private void hideImageStackAnimated() { private void hideImportDocumentButtonAnimated() { mImportButtonContainer.animate().alpha(0.0f); mButtonImportDocument.setEnabled(false); + mImportButtonContainer.setEnabled(false); } private void showNoPermissionView() { diff --git a/ginivision/src/main/java/net/gini/android/vision/help/SupportedFormatsAdapter.java b/ginivision/src/main/java/net/gini/android/vision/help/SupportedFormatsAdapter.java index 59dc8d516..aae62e3ca 100644 --- a/ginivision/src/main/java/net/gini/android/vision/help/SupportedFormatsAdapter.java +++ b/ginivision/src/main/java/net/gini/android/vision/help/SupportedFormatsAdapter.java @@ -4,6 +4,7 @@ import static net.gini.android.vision.help.SupportedFormatsAdapter.ItemType.HEADER; import static net.gini.android.vision.internal.util.FeatureConfiguration.getDocumentImportEnabledFileTypes; import static net.gini.android.vision.internal.util.FeatureConfiguration.isFileImportEnabled; +import static net.gini.android.vision.internal.util.FeatureConfiguration.isQRCodeScanningEnabled; import android.graphics.PorterDuff; import android.view.LayoutInflater; @@ -56,6 +57,9 @@ private List setUpItems( == DocumentImportEnabledFileTypes.PDF) { items.add(SupportedFormat.PDF); } + if (isQRCodeScanningEnabled(giniVisionFeatureConfiguration)) { + items.add(SupportedFormat.QR_CODE); + } items.add(SectionHeader.UNSUPPORTED_FORMATS); Collections.addAll(items, UnsupportedFormat.values()); return items; @@ -146,7 +150,8 @@ private enum SectionHeader { private enum SupportedFormat implements FormatInfo { PRINTED_INVOICES(R.string.gv_supported_format_printed_invoices), SINGLE_PAGE_AS_JPEG_PNG_GIF(R.string.gv_supported_format_single_page_as_jpeg_png_gif), - PDF(R.string.gv_supported_format_pdf); + PDF(R.string.gv_supported_format_pdf), + QR_CODE(R.string.gv_supported_format_qr_code); @DrawableRes private final int mIconBackground; diff --git a/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/HintPopup.kt b/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/HintPopup.kt new file mode 100644 index 000000000..14fb2f3ac --- /dev/null +++ b/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/HintPopup.kt @@ -0,0 +1,96 @@ +package net.gini.android.vision.internal.camera.view + +import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.ViewPropertyAnimatorCompat +import androidx.core.view.ViewPropertyAnimatorListenerAdapter + +/** + * Internal use only. + * + * @suppress + */ +internal class HintPopup( + private val popupView: View, + private val popupArrow: View, + closeButton: View, + private val animationDuration: Long, + private val onCloseClicked: () -> Unit) { + + private var popupAnimation: ViewPropertyAnimatorCompat? = null + private var popupArrowAnimation: ViewPropertyAnimatorCompat? = null + + var isShown = false + private set + + var isLastPopup = false + @JvmName("setIsLastPopup") set + + init { + closeButton.setOnClickListener { + onCloseClicked() + } + } + + fun show() { + popupView.visibility = View.VISIBLE + popupArrow.visibility = View.VISIBLE + clearUploadHintPopUpAnimations() + popupArrowAnimation = ViewCompat.animate( + popupView) + .alpha(1f) + .setDuration(animationDuration) + .setListener(object: ViewPropertyAnimatorListenerAdapter() { + override fun onAnimationEnd(view: View?) { + isShown = true + } + }) + .apply { + start() + } + popupAnimation = ViewCompat.animate( + popupArrow) + .alpha(1f) + .setDuration(animationDuration) + .apply { + start() + } + } + + fun hide(animatorListener: ViewPropertyAnimatorListenerAdapter?) { + clearUploadHintPopUpAnimations() + popupArrowAnimation = ViewCompat.animate(popupView) + .alpha(0f) + .setDuration(animationDuration) + .setListener(object : ViewPropertyAnimatorListenerAdapter() { + override fun onAnimationEnd(view: View) { + isShown = false + popupArrow.visibility = View.GONE + popupView.visibility = View.GONE + animatorListener?.onAnimationEnd(view) + } + }) + .apply { + start() + } + popupAnimation = ViewCompat.animate(popupArrow) + .alpha(0f) + .setDuration(animationDuration) + .apply { + start() + } + } + + private fun clearUploadHintPopUpAnimations() { + popupArrowAnimation?.apply { + cancel() + popupView.clearAnimation() + setListener(null) + } + popupAnimation?.apply { + cancel() + popupView.clearAnimation() + setListener(null) + } + } +} \ No newline at end of file diff --git a/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/QRCodePopup.kt b/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/QRCodePopup.kt new file mode 100644 index 000000000..b0059e282 --- /dev/null +++ b/ginivision/src/main/java/net/gini/android/vision/internal/camera/view/QRCodePopup.kt @@ -0,0 +1,116 @@ +package net.gini.android.vision.internal.camera.view + +import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.ViewPropertyAnimatorCompat +import androidx.core.view.ViewPropertyAnimatorListener +import androidx.core.view.ViewPropertyAnimatorListenerAdapter +import net.gini.android.vision.internal.ui.FragmentImplCallback + +/** + * Internal use only. + * + * @suppress + */ +internal class QRCodePopup @JvmOverloads constructor( + private val fragmentImplCallback: FragmentImplCallback, + private val popupView: View, + private val animationDuration: Long, + private val hideDelayMs: Long, + private val showAgainDelayMs: Long, + private val onClicked: (T?) -> Unit = {}) { + + private var animation: ViewPropertyAnimatorCompat? = null + private val hideRunnable: Runnable = Runnable { + hide() + } + + var qrCodeContent: T? = null + private set + + var isShown = false + private set + + init { + popupView.setOnClickListener { + onClicked(qrCodeContent) + hide() + } + } + + @JvmOverloads + fun show(qrCodeContent: T, startDelay: Long = 0) { + if (this.qrCodeContent != null && qrCodeContent != this.qrCodeContent) { + hide(object : ViewPropertyAnimatorListenerAdapter() { + override fun onAnimationEnd(view: View?) { + show(showAgainDelayMs) + } + }) + } else { + show(startDelay) + } + + this.qrCodeContent = qrCodeContent + } + + private fun show(startDelay: Long = 0) { + if (popupView.alpha != 0f) { + fragmentImplCallback.view?.removeCallbacks(hideRunnable) + fragmentImplCallback.view?.postDelayed(hideRunnable, hideDelayMs) + return + } + + clearQRCodeDetectedPopUpAnimation() + popupView.visibility = View.VISIBLE + animation = ViewCompat.animate(popupView) + .alpha(1.0f) + .setStartDelay(startDelay) + .setDuration(animationDuration) + .setListener(object : ViewPropertyAnimatorListenerAdapter() { + override fun onAnimationEnd(view: View?) { + isShown = true + } + }) + .apply { + start() + } + + fragmentImplCallback.view?.removeCallbacks(hideRunnable) + fragmentImplCallback.view?.postDelayed(hideRunnable, hideDelayMs) + } + + @JvmOverloads + fun hide(animatorListener: ViewPropertyAnimatorListener? = null) { + qrCodeContent = null + + if (popupView.alpha != 1f) { + animatorListener?.onAnimationEnd(popupView) + return + } + clearQRCodeDetectedPopUpAnimation() + animation = ViewCompat.animate(popupView) + .alpha(0.0f) + .setDuration(animationDuration) + .setListener(object : ViewPropertyAnimatorListenerAdapter() { + override fun onAnimationEnd(view: View) { + popupView.visibility = View.GONE + isShown = false + animatorListener?.onAnimationEnd(view) + } + }) + .apply { + start() + } + + fragmentImplCallback.view?.removeCallbacks(hideRunnable) + } + + private fun clearQRCodeDetectedPopUpAnimation() { + animation?.apply { + cancel() + popupView.clearAnimation() + setListener(null) + } + fragmentImplCallback.view?.removeCallbacks(hideRunnable) + } +} \ No newline at end of file diff --git a/ginivision/src/main/java/net/gini/android/vision/internal/qrcode/PaymentQRCodeReader.java b/ginivision/src/main/java/net/gini/android/vision/internal/qrcode/PaymentQRCodeReader.java index 7707ee8ce..2aa015d5c 100644 --- a/ginivision/src/main/java/net/gini/android/vision/internal/qrcode/PaymentQRCodeReader.java +++ b/ginivision/src/main/java/net/gini/android/vision/internal/qrcode/PaymentQRCodeReader.java @@ -32,6 +32,10 @@ public class PaymentQRCodeReader { public void onPaymentQRCodeDataAvailable( @NonNull final PaymentQRCodeData paymentQRCodeData) { } + + @Override + public void onNonPaymentQRCodeDetected(@NonNull final String qrCodeContent) { + } }; /** @@ -62,6 +66,7 @@ public void onQRCodesDetected(@NonNull final List qrCodes) { mListener.onPaymentQRCodeDataAvailable(paymentData); return; } catch (final IllegalArgumentException ignored) { + mListener.onNonPaymentQRCodeDetected(qrCodeContent); } } } @@ -109,5 +114,12 @@ public interface Listener { * @param paymentQRCodeData the payment data found on the image */ void onPaymentQRCodeDataAvailable(@NonNull final PaymentQRCodeData paymentQRCodeData); + + /** + * Called when a QRCode was found without a supported payment data format. + * + * @param qrCodeContent the content of the QRCode + */ + void onNonPaymentQRCodeDetected(@NonNull final String qrCodeContent); } } diff --git a/ginivision/src/main/res/layout-sw600dp-land/gv_fragment_camera.xml b/ginivision/src/main/res/layout-sw600dp-land/gv_fragment_camera.xml index 311e68f1c..10d53b6b9 100644 --- a/ginivision/src/main/res/layout-sw600dp-land/gv_fragment_camera.xml +++ b/ginivision/src/main/res/layout-sw600dp-land/gv_fragment_camera.xml @@ -1,5 +1,6 @@ @@ -93,7 +94,62 @@ android:layout_centerVertical="true" android:padding="@dimen/gv_camera_upload_hint_container_close_button_padding" android:src="@drawable/gv_hint_close" - android:tint="@color/gv_hint_close" /> + app:tint="@color/gv_hint_close" /> + + + + + + + + + + @@ -148,6 +204,57 @@ + + + + + + + + + + + tools:visibility="gone" /> @@ -135,7 +136,61 @@ android:layout_centerVertical="true" android:padding="@dimen/gv_camera_upload_hint_container_close_button_padding" android:src="@drawable/gv_hint_close" - android:tint="@color/gv_hint_close" /> + app:tint="@color/gv_hint_close" /> + + + + + + + + + + @@ -189,6 +244,57 @@ + + + + + + + + + + + tools:visibility="gone" /> @@ -143,7 +144,61 @@ android:layout_centerVertical="true" android:padding="@dimen/gv_camera_upload_hint_container_close_button_padding" android:src="@drawable/gv_hint_close" - android:tint="@color/gv_hint_close" /> + app:tint="@color/gv_hint_close" /> + + + + + + + + + + @@ -173,6 +228,7 @@ tools:ignore="ContentDescription" /> + + + + + + + + + + Cancel Use QR code? Continue + This QR Code does not contain any payment information. + X Ensure good lighting when photographing the invoice or remittance slip Flatten the page Hold device parallel over the page @@ -43,6 +45,7 @@ Tips for best results: To the camera Now documents can be conveniently uploaded for analysis. + Have you checked out the QR code scanner yet? The document could not be opened. Please try a different document. Permission to access files is required to upload saved invoices. Grant permission @@ -74,6 +77,7 @@ Computer-generated invoices and remittance slips 1-sided photos of format jpeg, png or gif PDF documents of up to 10 pages + QR Codes Handwriting Photos of monitors or screens The following formats are supported: diff --git a/ginivision/src/main/res/values/colors.xml b/ginivision/src/main/res/values/colors.xml index f27eecfe3..50a705dbb 100644 --- a/ginivision/src/main/res/values/colors.xml +++ b/ginivision/src/main/res/values/colors.xml @@ -27,6 +27,10 @@ #000 #009edc + #eeffffff + #e30b5d + #e30b5d + #ffffff #000000 diff --git a/ginivision/src/main/res/values/dimens.xml b/ginivision/src/main/res/values/dimens.xml index 610e5c8d7..848bb28a4 100644 --- a/ginivision/src/main/res/values/dimens.xml +++ b/ginivision/src/main/res/values/dimens.xml @@ -7,6 +7,7 @@ 16dp 8dp 8dp + 8dp 48dp 40dp 0dp diff --git a/ginivision/src/main/res/values/strings.xml b/ginivision/src/main/res/values/strings.xml index dab9b797c..a5db9b217 100644 --- a/ginivision/src/main/res/values/strings.xml +++ b/ginivision/src/main/res/values/strings.xml @@ -18,12 +18,16 @@ Zugriff erlauben Seiten Importieren - Seitenübersicht + Seitenübersicht Abbrechen - QR verwenden? + QR verwenden? Hier geht’s weiter. + + Dieser QR-Code enthält keine Bezahlinformationen. + X + Beim Abfotografieren der Rechnung oder des Überweisungsträgers auf gute Beleuchtung achten Seite glatt streichen Gerät parallel über die Seite halten @@ -57,6 +61,7 @@ Zur Kamera Es können jetzt auch ganz einfach Dateien hochgeladen werden. + Haben Sie schon den QR-Codeleser ausprobiert? Ihre Datei konnte nicht geöffnet werden. Bitte versuchen sie es mit einer anderen Datei. Nur mit Datei-Zugriff können gespeicherte Rechnungen hochgeladen werden. @@ -91,6 +96,7 @@ Computer-erstellte Überweisungsträger und Rechnungen Einseitige Bilder im jpeg, png oder gif Format PDF Dokumente von bis zu 10 Seiten + QR Codes Handschrift Fotos von Bildschirmen Folgende Formate werden unterstützt: diff --git a/ginivision/src/main/res/values/styles.xml b/ginivision/src/main/res/values/styles.xml index 232e11da1..5ad978c58 100644 --- a/ginivision/src/main/res/values/styles.xml +++ b/ginivision/src/main/res/values/styles.xml @@ -71,6 +71,18 @@ + + + +