From 705331cbf5d8d1da56213fddb4dba742873e1686 Mon Sep 17 00:00:00 2001 From: mozhganpeivandiansharbaf Date: Thu, 18 Sep 2025 12:00:52 +0200 Subject: [PATCH 1/7] feat(capture-sdk): Invoice marked as paid - Parse the new payment state object PP-1474 --- .../android/bank/api/models/Configuration.kt | 1 + .../api/response/ConfigurationResponse.kt | 4 +- .../exampleapp/ui/ConfigurationActivity.kt | 12 ++ .../exampleapp/ui/ConfigurationViewModel.kt | 3 +- .../sdk/exampleapp/ui/data/Configuration.kt | 6 +- .../res/layout/layout_analysis_toggles.xml | 7 + .../src/main/res/values/strings.xml | 1 + .../android/bank/sdk/capture/Configuration.kt | 8 + .../GiniCaptureDefaultNetworkService.kt | 1 + .../net/gini/android/capture/GiniCapture.java | 16 ++ .../capture/analysis/AnalysisFragment.java | 56 ++++++- .../analysis/AnalysisFragmentImpl.java | 7 +- .../analysis/AnalysisScreenContract.java | 5 +- .../analysis/AnalysisScreenPresenter.java | 40 ++++- .../AnalysisScreenPresenterExtension.kt | 3 + .../analysis/warning/WarningBottomSheet.kt | 154 ++++++++++++++++++ .../analysis/warning/WarningPaymentState.java | 20 +++ .../capture/analysis/warning/WarningType.java | 22 +++ .../di/CaptureSdkIsolatedKoinContext.kt | 3 +- .../android/capture/di/PaymentHintsModule.kt | 14 ++ .../capture/internal/network/Configuration.kt | 3 +- .../provider/GiniBankConfigurationProvider.kt | 1 + .../internal/ui/FragmentImplCallback.java | 4 +- .../GetPaymentHintsEnabledUseCase.kt | 11 ++ .../res/drawable/gc_bg_warning_circle.xml | 14 ++ .../sdk/src/main/res/drawable/gc_warning.xml | 10 ++ .../layout-land/gc_warning_bottom_sheet.xml | 87 ++++++++++ .../gc_warning_bottom_sheet.xml | 102 ++++++++++++ .../gc_warning_bottom_sheet.xml | 102 ++++++++++++ .../res/layout/gc_warning_bottom_sheet.xml | 104 ++++++++++++ .../sdk/src/main/res/values-en/strings.xml | 9 + .../sdk/src/main/res/values/dimens.xml | 12 ++ .../sdk/src/main/res/values/strings.xml | 10 ++ .../sdk/src/main/res/values/styles.xml | 5 + .../core/api/internal/GiniCoreAPIBuilder.kt | 2 +- 35 files changed, 842 insertions(+), 17 deletions(-) create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/di/PaymentHintsModule.kt create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetPaymentHintsEnabledUseCase.kt create mode 100644 capture-sdk/sdk/src/main/res/drawable/gc_bg_warning_circle.xml create mode 100644 capture-sdk/sdk/src/main/res/drawable/gc_warning.xml create mode 100644 capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml create mode 100644 capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml create mode 100644 capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml create mode 100644 capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml diff --git a/bank-api-library/library/src/main/java/net/gini/android/bank/api/models/Configuration.kt b/bank-api-library/library/src/main/java/net/gini/android/bank/api/models/Configuration.kt index b1b5794bac..3348c1fbbc 100644 --- a/bank-api-library/library/src/main/java/net/gini/android/bank/api/models/Configuration.kt +++ b/bank-api-library/library/src/main/java/net/gini/android/bank/api/models/Configuration.kt @@ -10,4 +10,5 @@ data class Configuration( val instantPaymentEnabled: Boolean, val isEInvoiceEnabled: Boolean, val qrCodeEducationEnabled: Boolean, + val paymentHintsEnabled: Boolean ) diff --git a/bank-api-library/library/src/main/java/net/gini/android/bank/api/response/ConfigurationResponse.kt b/bank-api-library/library/src/main/java/net/gini/android/bank/api/response/ConfigurationResponse.kt index 679b84b469..cef5abcd43 100644 --- a/bank-api-library/library/src/main/java/net/gini/android/bank/api/response/ConfigurationResponse.kt +++ b/bank-api-library/library/src/main/java/net/gini/android/bank/api/response/ConfigurationResponse.kt @@ -15,6 +15,7 @@ data class ConfigurationResponse( @Json(name = "qrCodeEducationEnabled") val qrCodeEducationEnabled: Boolean?, @Json(name = "instantPaymentEnabled") val instantPaymentEnabled: Boolean?, @Json(name = "eInvoiceEnabled") val eInvoiceEnabled: Boolean?, + @Json(name = "paymentHintsEnabled") val paymentHintsEnabled: Boolean?, ) internal fun ConfigurationResponse.toConfiguration() = Configuration( @@ -26,6 +27,7 @@ internal fun ConfigurationResponse.toConfiguration() = Configuration( transactionDocsEnabled = transactionDocsEnabled ?: false, qrCodeEducationEnabled = qrCodeEducationEnabled ?: false, instantPaymentEnabled = instantPaymentEnabled ?: false, - isEInvoiceEnabled = eInvoiceEnabled ?: false + isEInvoiceEnabled = eInvoiceEnabled ?: false, + paymentHintsEnabled = paymentHintsEnabled ?:false ) diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt index 696b85fa09..b2fe1a8032 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt @@ -224,6 +224,10 @@ class ConfigurationActivity : AppCompatActivity() { binding.layoutFeatureToggle.switchReturnAssistantFeature.isChecked = configuration.isReturnAssistantEnabled + // enable payment hints + binding.layoutAnalysisToggles.switchSetupPaymentHints.isChecked = + configuration.isPaymentHintsEnabled + // enable return reasons dialog binding.layoutReturnAssistantToggles.switchReturnReasonsDialog.isChecked = configuration.isReturnReasonsEnabled @@ -556,6 +560,14 @@ class ConfigurationActivity : AppCompatActivity() { ) } + binding.layoutAnalysisToggles.switchSetupPaymentHints.setOnCheckedChangeListener{ _, isChecked -> + configurationViewModel.setConfiguration( + configurationViewModel.configurationFlow.value.copy( + isPaymentHintsEnabled = isChecked + ) + ) + } + // enable supported format help screen binding.layoutHelpToggles.switchSupportedFormatsScreen.setOnCheckedChangeListener { _, isChecked -> configurationViewModel.setConfiguration( diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationViewModel.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationViewModel.kt index 6e7430cb9b..00e57eb225 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationViewModel.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationViewModel.kt @@ -121,7 +121,8 @@ class ConfigurationViewModel @Inject constructor( documentImportEnabledFileTypes = configuration.documentImportEnabledFileTypes, // enable bottom navigation bar bottomNavigationBarEnabled = configuration.isBottomNavigationBarEnabled, - + // enable payment hints + paymentHintsEnabled = configuration.isPaymentHintsEnabled, // enable onboarding screens at first launch showOnboardingAtFirstRun = configuration.isOnboardingAtFirstRunEnabled, // enable onboarding at every launch diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/Configuration.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/Configuration.kt index 38bbb54238..04a695c70b 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/Configuration.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/Configuration.kt @@ -151,6 +151,9 @@ data class Configuration( // enable return reasons dialog val isReturnReasonsEnabled: Boolean = false, + // enable show waring for paid invoices + val isPaymentHintsEnabled: Boolean = true, + // Digital invoice onboarding custom illustration val isDigitalInvoiceOnboardingCustomIllustrationEnabled: Boolean = false, @@ -187,7 +190,7 @@ data class Configuration( // enable Capture Sdk val isCaptureSDK: Boolean = false, -) : Parcelable { + ) : Parcelable { companion object { fun setupSDKWithDefaultConfiguration( @@ -203,6 +206,7 @@ data class Configuration( isFlashDefaultStateEnabled = defaultCaptureConfiguration.flashOnByDefault, documentImportEnabledFileTypes = defaultCaptureConfiguration.documentImportEnabledFileTypes, isBottomNavigationBarEnabled = defaultCaptureConfiguration.bottomNavigationBarEnabled, + isPaymentHintsEnabled = defaultCaptureConfiguration.paymentHintsEnabled, isOnboardingAtFirstRunEnabled = defaultCaptureConfiguration.showOnboardingAtFirstRun, isOnboardingAtEveryLaunchEnabled = defaultCaptureConfiguration.showOnboarding, isSupportedFormatsHelpScreenEnabled = defaultCaptureConfiguration.supportedFormatsHelpScreenEnabled, diff --git a/bank-sdk/example-app/src/main/res/layout/layout_analysis_toggles.xml b/bank-sdk/example-app/src/main/res/layout/layout_analysis_toggles.xml index 044c713233..c5d93fee34 100644 --- a/bank-sdk/example-app/src/main/res/layout/layout_analysis_toggles.xml +++ b/bank-sdk/example-app/src/main/res/layout/layout_analysis_toggles.xml @@ -28,4 +28,11 @@ android:layout_height="wrap_content" android:text="@string/screen_custom_loading_indicator_switch_description" /> + + \ No newline at end of file diff --git a/bank-sdk/example-app/src/main/res/values/strings.xml b/bank-sdk/example-app/src/main/res/values/strings.xml index 0b39c0ddc7..904da561e7 100644 --- a/bank-sdk/example-app/src/main/res/values/strings.xml +++ b/bank-sdk/example-app/src/main/res/values/strings.xml @@ -59,6 +59,7 @@ Please relaunch the app to use the default GiniConfiguration values. The configuration changes will take effect after closing this screen. SDK default configurations + Paid invoice warning Open with QR code scanning QR code scanning only diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/Configuration.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/Configuration.kt index c040e8481b..a40b1162a6 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/Configuration.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/Configuration.kt @@ -141,6 +141,13 @@ data class CaptureConfiguration( */ val bottomNavigationBarEnabled: Boolean = false, + /** + * Enable/disable the payment hint. + * + * On by default. + */ + val paymentHintsEnabled: Boolean = true, + /** * Set an adapter implementation to show a custom bottom navigation bar on the onboarding screen. */ @@ -247,6 +254,7 @@ internal fun GiniCapture.Builder.applyConfiguration(configuration: CaptureConfig .setGiniErrorLoggerIsOn(configuration.giniErrorLoggerIsOn) .setImportedFileSizeBytesLimit(configuration.importedFileSizeBytesLimit) .setBottomNavigationBarEnabled(configuration.bottomNavigationBarEnabled) + .setShowPaymentHintsEnabled(configuration.paymentHintsEnabled) .setEntryPoint(configuration.entryPoint) .setAllowScreenshots(configuration.allowScreenshots) .addCustomUploadMetadata(GiniBank.USER_COMMENT_GINI_BANK_VERSION, BuildConfig.VERSION_NAME) diff --git a/capture-sdk/default-network/src/main/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkService.kt b/capture-sdk/default-network/src/main/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkService.kt index e55f27fd4c..3b051fa746 100644 --- a/capture-sdk/default-network/src/main/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkService.kt +++ b/capture-sdk/default-network/src/main/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkService.kt @@ -202,6 +202,7 @@ internal constructor( isTransactionDocsEnabled = configuration.transactionDocsEnabled, isQrCodeEducationEnabled = configuration.qrCodeEducationEnabled, isInstantPaymentEnabled = configuration.instantPaymentEnabled, + paymentHintsEnabled = configuration.paymentHintsEnabled, isEInvoiceEnabled = configuration.isEInvoiceEnabled, amplitudeApiKey = configuration.amplitudeApiKey ?: "", ) diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/GiniCapture.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/GiniCapture.java index 9d29547f5e..0a5b5af0a3 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/GiniCapture.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/GiniCapture.java @@ -120,6 +120,7 @@ public class GiniCapture { private final InjectedViewAdapterInstance cameraNavigationBarBottomAdapterInstance; private final InjectedViewAdapterInstance errorNavigationBarBottomAdapterInstance; private final boolean isBottomNavigationBarEnabled; + private final boolean isPaymentHintsEnabled; private final InjectedViewAdapterInstance onboardingAlignCornersIllustrationAdapterInstance; private final InjectedViewAdapterInstance onboardingLightingIllustrationAdapterInstance; private final InjectedViewAdapterInstance onboardingMultiPageIllustrationAdapterInstance; @@ -427,6 +428,7 @@ private GiniCapture(@NonNull final Builder builder) { helpNavigationBarBottomAdapterInstance = builder.getHelpNavigationBarBottomAdapterInstance(); errorNavigationBarBottomAdapterInstance = builder.getErrorNavigationBarBottomAdapterInstance(); isBottomNavigationBarEnabled = builder.isBottomNavigationBarEnabled(); + isPaymentHintsEnabled = builder.isPaymentHintsEnabled(); onboardingAlignCornersIllustrationAdapterInstance = builder.getOnboardingAlignCornersIllustrationAdapterInstance(); onboardingLightingIllustrationAdapterInstance = builder.getOnboardingLightingIllustrationAdapterInstance(); onboardingMultiPageIllustrationAdapterInstance = builder.getOnboardingMultiPageIllustrationAdapterInstance(); @@ -715,6 +717,10 @@ public boolean isBottomNavigationBarEnabled() { return isBottomNavigationBarEnabled; } + public boolean isPaymentHintsEnabled() { + return isPaymentHintsEnabled; + } + @Nullable public OnboardingIllustrationAdapter getOnboardingAlignCornersIllustrationAdapter() { if (onboardingAlignCornersIllustrationAdapterInstance == null) { @@ -914,6 +920,7 @@ public void onAnalysisScreenEvent(@NotNull final Event even private InjectedViewAdapterInstance errorNavigationBarBottomAdapterInstance = new InjectedViewAdapterInstance<>(new DefaultErrorNavigationBarBottomAdapter()); private InjectedViewAdapterInstance cameraNavigationBarBottomAdapterInstance = new InjectedViewAdapterInstance<>(new DefaultCameraNavigationBarBottomAdapter()); private boolean isBottomNavigationBarEnabled = false; + private boolean isPaymentHintsEnabled = true; private InjectedViewAdapterInstance onboardingAlignCornersIllustrationAdapterInstance; private InjectedViewAdapterInstance onboardingLightingIllustrationAdapterInstance; private InjectedViewAdapterInstance onboardingMultiPageIllustrationAdapterInstance; @@ -1334,10 +1341,19 @@ public Builder setBottomNavigationBarEnabled(final Boolean enabled) { return this; } + public Builder setShowPaymentHintsEnabled(final Boolean enabled){ + isPaymentHintsEnabled = enabled; + return this; + } + private boolean isBottomNavigationBarEnabled() { return isBottomNavigationBarEnabled; } + private boolean isPaymentHintsEnabled(){ + return isPaymentHintsEnabled; + } + @NonNull private InjectedViewAdapterInstance getOnboardingAlignCornersIllustrationAdapterInstance() { return onboardingAlignCornersIllustrationAdapterInstance; diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragment.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragment.java index 898b7faee3..fdca70fd55 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragment.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragment.java @@ -8,18 +8,19 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; - import net.gini.android.capture.Document; +import net.gini.android.capture.analysis.warning.WarningType; import net.gini.android.capture.internal.ui.FragmentImplCallback; import net.gini.android.capture.internal.util.AlertDialogHelperCompat; import net.gini.android.capture.internal.util.CancelListener; +import net.gini.android.capture.analysis.warning.WarningBottomSheet; /** * Internal use only. @@ -27,6 +28,7 @@ public class AnalysisFragment extends Fragment implements FragmentImplCallback, AnalysisFragmentInterface { + private static final String WARNING_TAG = "WarningBottomSheet"; private AnalysisFragmentImpl mFragmentImpl; private AnalysisFragmentListener mListener; private CancelListener mCancelListener; @@ -66,7 +68,7 @@ public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) { @Nullable @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { + @Nullable final Bundle savedInstanceState) { return mFragmentImpl.onCreateView(inflater, container, savedInstanceState); } @@ -138,7 +140,6 @@ public void setCancelListener(@NonNull final CancelListener cancelListener) { * ReviewFragmentListener#onProceedToAnalysisScreen * (Document)} * @param documentAnalysisErrorMessage an optional error message shown to the user - * * @return a new instance of the Fragment */ public static AnalysisFragment createInstance(@NonNull final Document document, @@ -165,6 +166,53 @@ public void showAlertDialog(@NonNull final String message, cancelListener); } + @Override + public void showWarning(@NonNull WarningType type, @NonNull Runnable onProceed) { + FragmentManager fm = getParentFragmentManager(); + + // If already showing, just reattach the listener and return + Fragment existing = fm.findFragmentByTag(WARNING_TAG); + if (existing instanceof WarningBottomSheet) { + ((WarningBottomSheet) existing).setListener(new WarningBottomSheet.Listener() { + @Override + public void onCancelAction() { + if (mCancelListener != null) { + mCancelListener.onCancelFlow(); + } + } + + @Override + public void onProceedAction() { + onProceed.run(); + } + }); + return; + } + + WarningBottomSheet sheet = WarningBottomSheet.Companion.newInstance(type); + sheet.setCancelable(false); + + sheet.setListener(new WarningBottomSheet.Listener() { + @Override + public void onCancelAction() { + if (mCancelListener != null) { + mCancelListener.onCancelFlow(); + } + } + + @Override + public void onProceedAction() { + onProceed.run(); + } + }); + + if (!fm.isStateSaved()) { + sheet.show(fm, WARNING_TAG); + } else { + fm.beginTransaction().add(sheet, WARNING_TAG).commitAllowingStateLoss(); + } + } + @NonNull @Override public NavController findNavController() { diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragmentImpl.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragmentImpl.java index 0de84ed19e..23697418cb 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragmentImpl.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisFragmentImpl.java @@ -20,7 +20,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.compose.ui.platform.ComposeView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.FragmentActivity; @@ -28,6 +27,7 @@ import net.gini.android.capture.GiniCapture; import net.gini.android.capture.R; import net.gini.android.capture.analysis.education.EducationCompleteListener; +import net.gini.android.capture.analysis.warning.WarningType; import net.gini.android.capture.error.ErrorFragment; import net.gini.android.capture.error.ErrorType; import net.gini.android.capture.internal.ui.FragmentImplCallback; @@ -225,6 +225,11 @@ void showError(String error, Document document) { ); } + @Override + void showPaidWarningThen(@NonNull WarningType warningType, @NonNull Runnable onProceed) { + mFragment.showWarning(warningType ,onProceed); + } + @Override void showError(ErrorType errorType, Document document) { ErrorFragment.Companion.navigateToErrorFragment( diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenContract.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenContract.java index 1ffba1142d..87700a3ef7 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenContract.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenContract.java @@ -11,15 +11,13 @@ import net.gini.android.capture.GiniCaptureBasePresenter; import net.gini.android.capture.GiniCaptureBaseView; import net.gini.android.capture.analysis.education.EducationCompleteListener; +import net.gini.android.capture.analysis.warning.WarningType; import net.gini.android.capture.error.ErrorType; import net.gini.android.capture.internal.util.Size; import java.util.List; import jersey.repackaged.jsr166e.CompletableFuture; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; -import kotlin.jvm.functions.Function1; /** * Created by Alpar Szotyori on 08.05.2019. @@ -66,6 +64,7 @@ abstract void showAlertDialog(@NonNull final String message, abstract void showHints(List hints); abstract void showError(String errorMessage, Document document); + abstract void showPaidWarningThen(@NonNull WarningType warningType, @NonNull Runnable onProceed); abstract void showError(ErrorType errorType, Document document); abstract void showEducation(EducationCompleteListener listener); diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenter.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenter.java index 881b400651..3dfd8d7158 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenter.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenter.java @@ -10,6 +10,8 @@ import net.gini.android.capture.Document; import net.gini.android.capture.GiniCapture; import net.gini.android.capture.GiniCaptureError; +import net.gini.android.capture.analysis.warning.WarningPaymentState; +import net.gini.android.capture.analysis.warning.WarningType; import net.gini.android.capture.document.DocumentFactory; import net.gini.android.capture.document.GiniCaptureDocument; import net.gini.android.capture.document.GiniCaptureDocumentError; @@ -25,6 +27,7 @@ import net.gini.android.capture.internal.util.FileImportHelper; import net.gini.android.capture.logging.ErrorLog; import net.gini.android.capture.logging.ErrorLogger; +import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction; import net.gini.android.capture.tracking.AnalysisScreenEvent; import net.gini.android.capture.tracking.AnalysisScreenEvent.ERROR_DETAILS_MAP_KEY; @@ -55,6 +58,9 @@ class AnalysisScreenPresenter extends AnalysisScreenContract.Presenter { @VisibleForTesting static final String PARCELABLE_MEMORY_CACHE_TAG = "ANALYSIS_FRAGMENT"; + + private static final String EXTRACTION_PAYMENT_STATE = "paymentState"; + private static final Logger LOG = LoggerFactory.getLogger(AnalysisScreenPresenter.class); @VisibleForTesting @@ -167,7 +173,7 @@ public void start() { mStopped = false; checkGiniCaptureInstance(); if (mMultiPageDocument.getType() != Document.Type.XML && - mMultiPageDocument.getType() != Document.Type.XML_MULTI_PAGE) { + mMultiPageDocument.getType() != Document.Type.XML_MULTI_PAGE) { createDocumentRenderer(); } clearParcelableMemoryCache(); @@ -301,7 +307,8 @@ public Void apply(final AnalysisInteractor.ResultHolder resultHolder, } proceedSuccessNoExtractions(); break; - case SUCCESS_WITH_EXTRACTIONS: + case SUCCESS_WITH_EXTRACTIONS: { + mAnalysisCompleted = true; extension.getLastAnalyzedDocumentProvider() .update(remoteAnalyzedDocument); @@ -311,11 +318,19 @@ public Void apply(final AnalysisInteractor.ResultHolder resultHolder, } catch (Exception ignored) { } + if (resultHolder.getExtractions().isEmpty()) { proceedSuccessNoExtractions(); + } else if (shouldShowPaidInvoiceWarning(resultHolder)) { + getView().showPaidWarningThen( + WarningType.DOCUMENT_MARKED_AS_PAID, + () -> proceedWithExtractions(resultHolder) + ); } else { proceedWithExtractions(resultHolder); } + break; + } case NO_NETWORK_SERVICE: break; default: @@ -460,4 +475,25 @@ private void handleAnalysisError(final Throwable throwable) { } getView().showError(errorType, mMultiPageDocument); } + + private boolean shouldShowPaidInvoiceWarning( + @NonNull final AnalysisInteractor.ResultHolder resultHolder) { + // Feature flags / config + final boolean hintsEnabled = extension.getGetPaymentHintsEnabledUseCase().invoke(); + + final boolean showPaidWarningFlag = GiniCapture.hasInstance() && GiniCapture.getInstance().isPaymentHintsEnabled(); + + if (!hintsEnabled || !showPaidWarningFlag) { + return false; + } + + // Payment state + final Map exts = resultHolder.getExtractions(); + final GiniCaptureSpecificExtraction ps = exts.get(EXTRACTION_PAYMENT_STATE); + final String paymentStateValue = ps != null ? ps.getValue() : null; + final WarningPaymentState state = WarningPaymentState.from(paymentStateValue); + + return state.isPaid(); + } } + diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt index 54b62ed895..cb239cf21a 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt @@ -23,6 +23,7 @@ import net.gini.android.capture.internal.util.NullabilityHelper.getMapOrEmpty import net.gini.android.capture.network.model.GiniCaptureCompoundExtraction import net.gini.android.capture.network.model.GiniCaptureReturnReason import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction +import net.gini.android.capture.paymentHints.GetPaymentHintsEnabledUseCase import net.gini.android.capture.tracking.AnalysisScreenEvent import net.gini.android.capture.tracking.EventTrackingHelper @@ -32,6 +33,8 @@ internal class AnalysisScreenPresenterExtension( var listener: AnalysisFragmentListener? = null + val getPaymentHintsEnabledUseCase: + GetPaymentHintsEnabledUseCase by getGiniCaptureKoin().inject() val lastAnalyzedDocumentProvider: LastAnalyzedDocumentProvider by getGiniCaptureKoin().inject() diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt new file mode 100644 index 0000000000..33d12cff15 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt @@ -0,0 +1,154 @@ +package net.gini.android.capture.analysis.warning + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import net.gini.android.capture.R +import net.gini.android.capture.databinding.GcWarningBottomSheetBinding +import net.gini.android.capture.internal.util.getLayoutInflaterWithGiniCaptureTheme + + +class WarningBottomSheet : BottomSheetDialogFragment() { + + interface Listener { + fun onCancelAction() + fun onProceedAction() + } + + private lateinit var binding: GcWarningBottomSheetBinding + + private var titleText: CharSequence? = null + private var descText: CharSequence? = null + + var listener: Listener? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val type = arguments?.getSerializable(ARG_TYPE) as? WarningType + if (type != null) { + titleText = getString(type.titleRes) + descText = getString(type.descriptionRes) + } else { + // Fallback if nothing passed + titleText = arguments?.getCharSequence(ARG_TITLE) + descText = arguments?.getCharSequence(ARG_DESC) + } + } + + + override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater { + val inflater = super.onGetLayoutInflater(savedInstanceState) + return this.getLayoutInflaterWithGiniCaptureTheme(inflater) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + if (resources.getBoolean(R.bool.gc_is_tablet)) { + activity?.let { + binding = GcWarningBottomSheetBinding.inflate(getLayoutInflaterWithGiniCaptureTheme(it.layoutInflater), null, false) + + val builder = AlertDialog.Builder(context) + builder.setView(binding.root) + bindUi() + + val alertDialog = builder.create() + return alertDialog + } + } + + return super.onCreateDialog(savedInstanceState) + } + + override fun getTheme(): Int { + if (resources.getBoolean(R.bool.gc_is_tablet)) { + return super.getTheme() + } + return super.getTheme() + // Use your app theme for bottom sheets +// return R.style.GiniCaptureTheme_BottomSheetDialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + if (resources.getBoolean(R.bool.gc_is_tablet)) { + return super.onCreateView(inflater, container, savedInstanceState) + } + + binding = GcWarningBottomSheetBinding.inflate(inflater, container, false) + handleBottomSheetConfigurations() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + ViewCompat.setAccessibilityPaneTitle(view, getString(R.string.gc_warning_title_dialog)) + bindUi() + } + private fun handleBottomSheetConfigurations() { + dialog?.setOnShowListener { + val bottomSheetInternal = + (it as? BottomSheetDialog)?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + (it as? BottomSheetDialog)?.setCanceledOnTouchOutside(false) + + bottomSheetInternal?.let { + BottomSheetBehavior.from(bottomSheetInternal).apply { + isDraggable = false + isHideable = false + state = BottomSheetBehavior.STATE_EXPANDED + addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState in listOf( + BottomSheetBehavior.STATE_HIDDEN, + STATE_COLLAPSED + ) + ) + dismiss() + + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit + }) + } + } + } + } + private fun bindUi() { + binding.warningTitle.text = titleText + binding.warningDescription.text = descText + // Icon is decorative only + binding.warningIcon?.contentDescription = null + binding.warningIcon?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + + binding.primaryButton.setOnClickListener { + listener?.onCancelAction() + dismissAllowingStateLoss() + } + binding.secondaryButton.setOnClickListener { + listener?.onProceedAction() + dismissAllowingStateLoss() + } + } + + companion object { + private const val ARG_TYPE = "arg_type" + private const val ARG_TITLE = "arg_title" // kept for backward-compat if needed + private const val ARG_DESC = "arg_desc" + + @JvmStatic + fun newInstance(type: WarningType) = WarningBottomSheet().apply { + arguments = Bundle().apply { + putSerializable(ARG_TYPE, type) // WarningType is an enum (Serializable) + } + } + } +} \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java new file mode 100644 index 0000000000..c6e21cab32 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java @@ -0,0 +1,20 @@ +package net.gini.android.capture.analysis.warning; + +import androidx.annotation.Nullable; + + + public enum WarningPaymentState { + PAID, TO_BE_PAID, BOOKED, UNKNOWN; + + public static WarningPaymentState from(@Nullable String raw) { + if (raw == null) return UNKNOWN; + switch (raw.trim()) { + case "Paid": return PAID; + case "ToBePaid": return TO_BE_PAID; // in future versions this might be "ToBePaid" + case "booked": return BOOKED; // in future versions this might be "Booked" + default: return UNKNOWN; + } + } + + public boolean isPaid() { return this == PAID; } +} diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java new file mode 100644 index 0000000000..f70d7961cf --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java @@ -0,0 +1,22 @@ +package net.gini.android.capture.analysis.warning; + +import androidx.annotation.StringRes; + +import net.gini.android.capture.R; + +public enum WarningType { + DOCUMENT_MARKED_AS_PAID( + R.string.gc_document_marked_paid_title, + R.string.gc_document_marked_paid_desc + ); + + @StringRes private final int titleRes; + @StringRes private final int descriptionRes; + + WarningType(@StringRes int titleRes, @StringRes int descriptionRes) { + this.titleRes = titleRes; + this.descriptionRes = descriptionRes; + } + public int getTitleRes() { return titleRes; } + public int getDescriptionRes() { return descriptionRes; } +} diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/CaptureSdkIsolatedKoinContext.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/CaptureSdkIsolatedKoinContext.kt index 34089c69fb..d7040ebd65 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/CaptureSdkIsolatedKoinContext.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/CaptureSdkIsolatedKoinContext.kt @@ -11,7 +11,8 @@ object CaptureSdkIsolatedKoinContext { educationModule, qrEducationModule, invoiceEducationModule, - EInvoiceModule + EInvoiceModule, + PaymentHintsModule ) } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/PaymentHintsModule.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/PaymentHintsModule.kt new file mode 100644 index 0000000000..9d29c1b6cd --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/di/PaymentHintsModule.kt @@ -0,0 +1,14 @@ +package net.gini.android.capture.di + +import net.gini.android.capture.paymentHints.GetPaymentHintsEnabledUseCase +import org.koin.dsl.module + +internal val PaymentHintsModule = module { + + factory { + GetPaymentHintsEnabledUseCase( + giniBankConfigurationProvider = get(), + ) + } + +} diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/network/Configuration.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/network/Configuration.kt index b074c2e967..d0a6c822a9 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/network/Configuration.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/network/Configuration.kt @@ -12,5 +12,6 @@ data class Configuration( val isQrCodeEducationEnabled: Boolean, val isInstantPaymentEnabled: Boolean, val isEInvoiceEnabled: Boolean, - val amplitudeApiKey: String + val amplitudeApiKey: String, + val paymentHintsEnabled: Boolean, ) diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/provider/GiniBankConfigurationProvider.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/provider/GiniBankConfigurationProvider.kt index 2ed6b8a5ab..988614a540 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/provider/GiniBankConfigurationProvider.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/provider/GiniBankConfigurationProvider.kt @@ -14,6 +14,7 @@ class GiniBankConfigurationProvider { isQrCodeEducationEnabled = false, isInstantPaymentEnabled = false, isEInvoiceEnabled = false, + paymentHintsEnabled = false ) fun provide(): Configuration = configuration diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/ui/FragmentImplCallback.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/ui/FragmentImplCallback.java index 25918069e7..de9f27631e 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/ui/FragmentImplCallback.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/ui/FragmentImplCallback.java @@ -13,7 +13,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.navigation.NavController; import androidx.navigation.NavDirections; - +import net.gini.android.capture.analysis.warning.WarningType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +47,8 @@ void showAlertDialog(@NonNull final String message, @NonNull FragmentManager getChildFragmentManager(); + default void showWarning(@NonNull WarningType warningType, @NonNull Runnable onProceed) {} + @NonNull FragmentManager getParentFragmentManager(); diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetPaymentHintsEnabledUseCase.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetPaymentHintsEnabledUseCase.kt new file mode 100644 index 0000000000..0c2a961352 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetPaymentHintsEnabledUseCase.kt @@ -0,0 +1,11 @@ +package net.gini.android.capture.paymentHints + +import net.gini.android.capture.internal.provider.GiniBankConfigurationProvider + + +class GetPaymentHintsEnabledUseCase( + private val giniBankConfigurationProvider: GiniBankConfigurationProvider, +) { + operator fun invoke() = giniBankConfigurationProvider.provide().paymentHintsEnabled + +} diff --git a/capture-sdk/sdk/src/main/res/drawable/gc_bg_warning_circle.xml b/capture-sdk/sdk/src/main/res/drawable/gc_bg_warning_circle.xml new file mode 100644 index 0000000000..9b7d7a307d --- /dev/null +++ b/capture-sdk/sdk/src/main/res/drawable/gc_bg_warning_circle.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/capture-sdk/sdk/src/main/res/drawable/gc_warning.xml b/capture-sdk/sdk/src/main/res/drawable/gc_warning.xml new file mode 100644 index 0000000000..da2320c761 --- /dev/null +++ b/capture-sdk/sdk/src/main/res/drawable/gc_warning.xml @@ -0,0 +1,10 @@ + + + diff --git a/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml new file mode 100644 index 0000000000..bb8cb8b006 --- /dev/null +++ b/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml new file mode 100644 index 0000000000..a4e346c730 --- /dev/null +++ b/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml new file mode 100644 index 0000000000..a4e346c730 --- /dev/null +++ b/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml new file mode 100644 index 0000000000..0dfc7ef3e0 --- /dev/null +++ b/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/capture-sdk/sdk/src/main/res/values-en/strings.xml b/capture-sdk/sdk/src/main/res/values-en/strings.xml index 895c5e46a7..cca20b6c92 100644 --- a/capture-sdk/sdk/src/main/res/values-en/strings.xml +++ b/capture-sdk/sdk/src/main/res/values-en/strings.xml @@ -173,4 +173,13 @@ You can upload PDFs, images, or GIFs analyzing invoice in progress + + + Cancel transfer + Proceed anyway + Document marked as paid + This document states that it has already been paid. Please check the note in the document to be sure. + Warning Dialog + + \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/res/values/dimens.xml b/capture-sdk/sdk/src/main/res/values/dimens.xml index aa0eb7ea0f..4b8177fc16 100644 --- a/capture-sdk/sdk/src/main/res/values/dimens.xml +++ b/capture-sdk/sdk/src/main/res/values/dimens.xml @@ -49,4 +49,16 @@ 55dp 60dp + 8dp + 12dp + 16dp + 24dp + 16dp + 8dp + 16dp + 16dp + 16dp + 24dp + 8dp + 24dp diff --git a/capture-sdk/sdk/src/main/res/values/strings.xml b/capture-sdk/sdk/src/main/res/values/strings.xml index 233095d59e..d54b07e221 100644 --- a/capture-sdk/sdk/src/main/res/values/strings.xml +++ b/capture-sdk/sdk/src/main/res/values/strings.xml @@ -191,5 +191,15 @@ Laden Sie direkt ein PDF, Bild oder GIF hoch Laden Sie direkt ein PDF, Bild oder GIF hoch analyse der Rechnung läuft + + + Überweisung abbrechen + Trotzdem fortfahren + Dokument als bezahlt gekennzeichnet + + Auf diesem Dokument steht, dass es bereits bezahlt wurde. Bitte prüfen Sie den Hinweis in dem Dokument, um sicherzugehen. + + Warndialog + diff --git a/capture-sdk/sdk/src/main/res/values/styles.xml b/capture-sdk/sdk/src/main/res/values/styles.xml index 8cc0d1d847..794ef59709 100644 --- a/capture-sdk/sdk/src/main/res/values/styles.xml +++ b/capture-sdk/sdk/src/main/res/values/styles.xml @@ -83,4 +83,9 @@ + diff --git a/core-api-library/library/src/main/java/net/gini/android/core/api/internal/GiniCoreAPIBuilder.kt b/core-api-library/library/src/main/java/net/gini/android/core/api/internal/GiniCoreAPIBuilder.kt index 700ebc97b4..b1988a6596 100644 --- a/core-api-library/library/src/main/java/net/gini/android/core/api/internal/GiniCoreAPIBuilder.kt +++ b/core-api-library/library/src/main/java/net/gini/android/core/api/internal/GiniCoreAPIBuilder.kt @@ -70,7 +70,7 @@ abstract class GiniCoreAPIBuilder, G : GiniCoreAPI Date: Thu, 18 Sep 2025 14:37:44 +0200 Subject: [PATCH 2/7] fix(capture-sdk): enhance warning bottom sheet layout and accessibility PP-1474 --- .../exampleapp/ui/ConfigurationActivity.kt | 1 + .../analysis/warning/WarningBottomSheet.kt | 80 ++++++++++++------- .../analysis/warning/WarningPaymentState.java | 12 +++ .../capture/analysis/warning/WarningType.java | 6 ++ .../layout-land/gc_warning_bottom_sheet.xml | 30 +++---- .../gc_warning_bottom_sheet.xml | 14 ++-- .../gc_warning_bottom_sheet.xml | 14 ++-- .../res/layout/gc_warning_bottom_sheet.xml | 10 +-- .../sdk/src/main/res/values-en/strings.xml | 1 + .../sdk/src/main/res/values/dimens.xml | 13 --- .../sdk/src/main/res/values/strings.xml | 1 + .../sdk/src/main/res/values/styles.xml | 3 + .../core/api/internal/GiniCoreAPIBuilder.kt | 2 +- 13 files changed, 108 insertions(+), 79 deletions(-) diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt index b2fe1a8032..8aba9bba22 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/ConfigurationActivity.kt @@ -560,6 +560,7 @@ class ConfigurationActivity : AppCompatActivity() { ) } + //enable payment hints for showing warning binding.layoutAnalysisToggles.switchSetupPaymentHints.setOnCheckedChangeListener{ _, isChecked -> configurationViewModel.setConfiguration( configurationViewModel.configurationFlow.value.copy( diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt index 33d12cff15..0af42d2c2a 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningBottomSheet.kt @@ -15,6 +15,17 @@ import net.gini.android.capture.R import net.gini.android.capture.databinding.GcWarningBottomSheetBinding import net.gini.android.capture.internal.util.getLayoutInflaterWithGiniCaptureTheme +/** + * A reusable bottom sheet (or dialog on tablets) that displays a warning message. + * + * Responsibilities: + * - Presents warning information to the user in a modal sheet/dialog. + * - Provides two actions: cancel (primary button) and proceed (secondary button). + * - Handles accessibility (content descriptions, pane title). + * - Adapts its layout and behavior for phones (BottomSheetDialog) and tablets (AlertDialog). + * + * Use [newInstance] with a [WarningType] to configure the title and description. + */ class WarningBottomSheet : BottomSheetDialogFragment() { @@ -30,16 +41,13 @@ class WarningBottomSheet : BottomSheetDialogFragment() { var listener: Listener? = null + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val type = arguments?.getSerializable(ARG_TYPE) as? WarningType if (type != null) { titleText = getString(type.titleRes) - descText = getString(type.descriptionRes) - } else { - // Fallback if nothing passed - titleText = arguments?.getCharSequence(ARG_TITLE) - descText = arguments?.getCharSequence(ARG_DESC) + descText = getString(type.descriptionRes) } } @@ -52,7 +60,11 @@ class WarningBottomSheet : BottomSheetDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { if (resources.getBoolean(R.bool.gc_is_tablet)) { activity?.let { - binding = GcWarningBottomSheetBinding.inflate(getLayoutInflaterWithGiniCaptureTheme(it.layoutInflater), null, false) + binding = GcWarningBottomSheetBinding.inflate( + getLayoutInflaterWithGiniCaptureTheme(it.layoutInflater), + null, + false + ) val builder = AlertDialog.Builder(context) builder.setView(binding.root) @@ -70,9 +82,7 @@ class WarningBottomSheet : BottomSheetDialogFragment() { if (resources.getBoolean(R.bool.gc_is_tablet)) { return super.getTheme() } - return super.getTheme() - // Use your app theme for bottom sheets -// return R.style.GiniCaptureTheme_BottomSheetDialog + return R.style.GiniCaptureTheme_BottomSheetDialog } override fun onCreateView( @@ -94,26 +104,36 @@ class WarningBottomSheet : BottomSheetDialogFragment() { ViewCompat.setAccessibilityPaneTitle(view, getString(R.string.gc_warning_title_dialog)) bindUi() } + private fun handleBottomSheetConfigurations() { - dialog?.setOnShowListener { - val bottomSheetInternal = - (it as? BottomSheetDialog)?.findViewById(com.google.android.material.R.id.design_bottom_sheet) - (it as? BottomSheetDialog)?.setCanceledOnTouchOutside(false) + dialog?.setOnShowListener { dlg -> + val bottomSheetInternal = (dlg as? BottomSheetDialog) + ?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?: return@setOnShowListener + + dlg.setCanceledOnTouchOutside(false) - bottomSheetInternal?.let { + this.isCancelable = false + + bottomSheetInternal.let { BottomSheetBehavior.from(bottomSheetInternal).apply { isDraggable = false isHideable = false + skipCollapsed = true state = BottomSheetBehavior.STATE_EXPANDED addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState in listOf( - BottomSheetBehavior.STATE_HIDDEN, - STATE_COLLAPSED - ) - ) - dismiss() - + override fun onStateChanged(bs: View, newState: Int) { + when (newState) { + BottomSheetBehavior.STATE_DRAGGING, + BottomSheetBehavior.STATE_SETTLING, + BottomSheetBehavior.STATE_COLLAPSED -> { + state = BottomSheetBehavior.STATE_EXPANDED + } + BottomSheetBehavior.STATE_HIDDEN -> { + state = BottomSheetBehavior.STATE_EXPANDED + } + else -> Unit + } } override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit @@ -122,28 +142,26 @@ class WarningBottomSheet : BottomSheetDialogFragment() { } } } + private fun bindUi() { binding.warningTitle.text = titleText binding.warningDescription.text = descText - // Icon is decorative only - binding.warningIcon?.contentDescription = null - binding.warningIcon?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - binding.primaryButton.setOnClickListener { + binding.warningIcon?.contentDescription = getString(R.string.warning_icon_description) + binding.warningIcon?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES + + binding.cancelButton.setOnClickListener { listener?.onCancelAction() dismissAllowingStateLoss() } - binding.secondaryButton.setOnClickListener { + binding.proceedButton.setOnClickListener { listener?.onProceedAction() dismissAllowingStateLoss() } } companion object { - private const val ARG_TYPE = "arg_type" - private const val ARG_TITLE = "arg_title" // kept for backward-compat if needed - private const val ARG_DESC = "arg_desc" - + private const val ARG_TYPE = "arg_type" @JvmStatic fun newInstance(type: WarningType) = WarningBottomSheet().apply { arguments = Bundle().apply { diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java index c6e21cab32..2d71e9d62d 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningPaymentState.java @@ -3,6 +3,18 @@ import androidx.annotation.Nullable; +/** + * Represents the normalized payment state of a document (e.g. invoice, receipt) + * extracted during analysis. + * + * Responsibilities: + * - Encapsulates the set of supported payment states that the app cares about + * (PAID, TO_BE_PAID, BOOKED, UNKNOWN). + * - Provides a safe conversion from raw string values (via {@link #from(String)}) + * into one of the enum constants, handling nulls and unexpected values gracefully. + * - Offers convenience methods (e.g. {@link #isPaid()}) to simplify + * conditional checks in presenters or views. + */ public enum WarningPaymentState { PAID, TO_BE_PAID, BOOKED, UNKNOWN; diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java index f70d7961cf..3cb50ccc2c 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java @@ -4,6 +4,12 @@ import net.gini.android.capture.R; +/** + * Represents the different warning scenarios that can be shown in the UI + * (e.g. inside {@link WarningBottomSheet}). + * Each enum value holds the resource IDs for its title and description, + * so that the UI can easily fetch localized strings when displaying the warning. + */ public enum WarningType { DOCUMENT_MARKED_AS_PAID( R.string.gc_document_marked_paid_title, diff --git a/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml index bb8cb8b006..6b2822e653 100644 --- a/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml +++ b/capture-sdk/sdk/src/main/res/layout-land/gc_warning_bottom_sheet.xml @@ -13,13 +13,13 @@ + android:padding="@dimen/gc_large"> + android:paddingBottom="@dimen/gc_medium" /> diff --git a/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml index a4e346c730..17859c9c2e 100644 --- a/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml +++ b/capture-sdk/sdk/src/main/res/layout-sw600dp-land/gc_warning_bottom_sheet.xml @@ -12,7 +12,7 @@ android:id="@+id/WaringBottomSheet" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="@dimen/bs_content_padding"> + android:padding="@dimen/gc_large"> + android:layout_marginStart="@dimen/gc_medium_12"> + android:paddingBottom="@dimen/gc_medium" /> + app:layout_constraintTop_toBottomOf="@id/cancel_button" /> diff --git a/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml index a4e346c730..17859c9c2e 100644 --- a/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml +++ b/capture-sdk/sdk/src/main/res/layout-sw600dp/gc_warning_bottom_sheet.xml @@ -12,7 +12,7 @@ android:id="@+id/WaringBottomSheet" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="@dimen/bs_content_padding"> + android:padding="@dimen/gc_large"> + android:layout_marginStart="@dimen/gc_medium_12"> + android:paddingBottom="@dimen/gc_medium" /> + app:layout_constraintTop_toBottomOf="@id/cancel_button" /> diff --git a/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml b/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml index 0dfc7ef3e0..e341aefda5 100644 --- a/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml +++ b/capture-sdk/sdk/src/main/res/layout/gc_warning_bottom_sheet.xml @@ -14,7 +14,7 @@ + android:padding="@dimen/gc_large"> + android:paddingBottom="@dimen/gc_medium" /> diff --git a/capture-sdk/sdk/src/main/res/values-en/strings.xml b/capture-sdk/sdk/src/main/res/values-en/strings.xml index cca20b6c92..43d0688d09 100644 --- a/capture-sdk/sdk/src/main/res/values-en/strings.xml +++ b/capture-sdk/sdk/src/main/res/values-en/strings.xml @@ -180,6 +180,7 @@ Document marked as paid This document states that it has already been paid. Please check the note in the document to be sure. Warning Dialog + Warning icon \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/res/values/dimens.xml b/capture-sdk/sdk/src/main/res/values/dimens.xml index 4b8177fc16..fc8b3792b3 100644 --- a/capture-sdk/sdk/src/main/res/values/dimens.xml +++ b/capture-sdk/sdk/src/main/res/values/dimens.xml @@ -48,17 +48,4 @@ 330dp 55dp 60dp - - 8dp - 12dp - 16dp - 24dp - 16dp - 8dp - 16dp - 16dp - 16dp - 24dp - 8dp - 24dp diff --git a/capture-sdk/sdk/src/main/res/values/strings.xml b/capture-sdk/sdk/src/main/res/values/strings.xml index d54b07e221..fc8a7fdfda 100644 --- a/capture-sdk/sdk/src/main/res/values/strings.xml +++ b/capture-sdk/sdk/src/main/res/values/strings.xml @@ -200,6 +200,7 @@ Auf diesem Dokument steht, dass es bereits bezahlt wurde. Bitte prüfen Sie den Hinweis in dem Dokument, um sicherzugehen. Warndialog + Warnsymbol diff --git a/capture-sdk/sdk/src/main/res/values/styles.xml b/capture-sdk/sdk/src/main/res/values/styles.xml index 794ef59709..1c1dec542d 100644 --- a/capture-sdk/sdk/src/main/res/values/styles.xml +++ b/capture-sdk/sdk/src/main/res/values/styles.xml @@ -74,6 +74,9 @@ ?attr/backgroundColor +