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 84fad64323..8661b0698b 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 @@ -13,4 +13,5 @@ data class Configuration( val isSavePhotosLocallyEnabled: Boolean, val isAlreadyPaidHintEnabled: Boolean, val isPaymentDueHintEnabled: Boolean, + val isCreditNoteHintEnabled: 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 d5fbf2656c..009fae626e 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 @@ -18,6 +18,7 @@ data class ConfigurationResponse( @Json(name = "alreadyPaidHintEnabled") val alreadyPaidHintEnabled: Boolean?, @Json(name = "paymentDueHintEnabled") val paymentDueHintEnabled: Boolean?, @Json(name = "savePhotosLocallyEnabled") val savePhotosLocallyEnabled: Boolean?, + @Json(name = "creditNoteHintEnabled") val creditNoteHintEnabled: Boolean?, ) internal fun ConfigurationResponse.toConfiguration() = Configuration( @@ -33,5 +34,6 @@ internal fun ConfigurationResponse.toConfiguration() = Configuration( isAlreadyPaidHintEnabled = alreadyPaidHintEnabled ?: false, isPaymentDueHintEnabled = paymentDueHintEnabled ?: false, isSavePhotosLocallyEnabled = savePhotosLocallyEnabled ?: false, + isCreditNoteHintEnabled = creditNoteHintEnabled ?: false, ) diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/CaptureSdkStandAloneActivity.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/CaptureSdkStandAloneActivity.kt index 2968093738..c1a70708fa 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/CaptureSdkStandAloneActivity.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/CaptureSdkStandAloneActivity.kt @@ -55,7 +55,9 @@ class CaptureSdkStandAloneActivity : AppCompatActivity() { private fun restoreFragmentListener() { val fragment = - supportFragmentManager.findFragmentByTag(ClientCaptureSDKFragment::class.java.name) as? ClientCaptureSDKFragment? + supportFragmentManager.findFragmentByTag( + ClientCaptureSDKFragment::class.java.name + ) as? ClientCaptureSDKFragment? listener?.let { fragment?.setListener(it) } } @@ -83,4 +85,4 @@ class ClientCaptureSDKFragmentFactory( else -> super.instantiate(classLoader, className) } } -} \ No newline at end of file +} 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 d587165791..3fe51e8c36 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 @@ -25,6 +25,7 @@ import net.gini.android.capture.util.SharedPreferenceHelper import net.gini.android.capture.util.SharedPreferenceHelper.SAF_STORAGE_URI_KEY import javax.inject.Inject +@Suppress("LargeClass") @AndroidEntryPoint class ConfigurationActivity : AppCompatActivity() { @@ -109,6 +110,7 @@ class ConfigurationActivity : AppCompatActivity() { finish() } + @Suppress("LongMethod") private fun updateUIWithConfigurationObject(configuration: ExampleAppBankConfiguration) { // setup sdk with default configuration binding.layoutFeatureToggle.switchSetupSdkWithDefaultConfiguration.isChecked = @@ -118,7 +120,8 @@ class ConfigurationActivity : AppCompatActivity() { // Capture SDK binding.layoutFeatureToggle.switchCaptureSdk.isChecked = configuration.isCaptureSDK // Saving Invoices Locally - binding.layoutFeatureToggle.switchSaveInvoicesLocallyFeature.isChecked = configuration.saveInvoicesLocallyEnabled + binding.layoutFeatureToggle.switchSaveInvoicesLocallyFeature.isChecked = + configuration.saveInvoicesLocallyEnabled // QR code scanning binding.layoutFeatureToggle.switchQrCodeScanning.isChecked = configuration.isQrCodeEnabled // only QR code scanning @@ -240,6 +243,10 @@ class ConfigurationActivity : AppCompatActivity() { binding.layoutFeatureToggle.editTextPaymentDueHintThresholdDays.hint = configuration.paymentDueHintThresholdDays.toString() + // enable credit note hint + binding.layoutFeatureToggle.switchCreditNoteHint.isChecked = + configuration.isCreditNoteHintEnabled + // enable return reasons dialog binding.layoutReturnAssistantToggles.switchReturnReasonsDialog.isChecked = configuration.isReturnReasonsEnabled @@ -602,6 +609,14 @@ class ConfigurationActivity : AppCompatActivity() { } } + //enable credit note hint for showing warning + binding.layoutFeatureToggle.switchCreditNoteHint.setOnCheckedChangeListener{ _, isChecked -> + configurationViewModel.setConfiguration( + configurationViewModel.configurationFlow.value.copy( + isCreditNoteHintEnabled = isChecked + ) + ) + } // enable supported format help screen binding.layoutHelpToggles.switchSupportedFormatsScreen.setOnCheckedChangeListener { _, isChecked -> 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 abd64b6ca3..ad237113f1 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,12 +121,14 @@ class ConfigurationViewModel @Inject constructor( documentImportEnabledFileTypes = configuration.documentImportEnabledFileTypes, // enable bottom navigation bar bottomNavigationBarEnabled = configuration.isBottomNavigationBarEnabled, - // enable payment hints + // enable already paid hint alreadyPaidHintEnabled = configuration.isAlreadyPaidHintEnabled, // enable payment due hint paymentDueHintEnabled = configuration.isPaymentDueHintEnabled, // set payment due hint threshold days paymentDueHintThresholdDays = configuration.paymentDueHintThresholdDays, + // enable credit note hint + creditNoteHintEnabled = configuration.isCreditNoteHintEnabled, // 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/ExampleAppBankConfiguration.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/ExampleAppBankConfiguration.kt index be39fb6646..80888a174e 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/ExampleAppBankConfiguration.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/data/ExampleAppBankConfiguration.kt @@ -39,15 +39,18 @@ data class ExampleAppBankConfiguration( val isFlashDefaultStateEnabled: Boolean = false, // set import document type support - // net.gini.android.capture.GiniCapture.Builder#setDocumentImportEnabledFileTypes → radio buttons to select an available enum value - val documentImportEnabledFileTypes: DocumentImportEnabledFileTypes = DocumentImportEnabledFileTypes.PDF_AND_IMAGES, + // net.gini.android.capture.GiniCapture.Builder#setDocumentImportEnabledFileTypes → + // radio buttons to select an available enum value + val documentImportEnabledFileTypes: DocumentImportEnabledFileTypes = + DocumentImportEnabledFileTypes.PDF_AND_IMAGES, // enable bottom navigation bar // net.gini.android.capture.GiniCapture.Builder#setBottomNavigationBarEnabled → on/off switch val isBottomNavigationBarEnabled: Boolean = false, // enable Help screens custom bottom navigation bar - // net.gini.android.capture.GiniCapture.Builder#setHelpNavigationBarBottomAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setHelpNavigationBarBottomAdapter → + // on/off switch to show a custom adapter implementation val isHelpScreensCustomBottomNavBarEnabled: Boolean = false, // enable Error screens custom bottom navigation bar @@ -56,11 +59,13 @@ data class ExampleAppBankConfiguration( val isErrorScreensCustomBottomNavBarEnabled: Boolean = false, // enable camera screens custom bottom navigation bar - // net.gini.android.capture.GiniCapture.Builder#setCameraNavigationBarBottomAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setCameraNavigationBarBottomAdapter → + // on/off switch to show a custom adapter implementation val isCameraBottomNavBarEnabled: Boolean = false, // enable review screens custom bottom navigation bar - // net.gini.android.capture.GiniCapture.Builder#setReviewBottomBarNavigationAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setReviewBottomBarNavigationAdapter → + // on/off switch to show a custom adapter implementation val isReviewScreenCustomBottomNavBarEnabled: Boolean = false, // enable image picker screens custom bottom navigation bar -> was implemented on iOS, not needed for Android @@ -74,7 +79,8 @@ data class ExampleAppBankConfiguration( val isOnboardingAtEveryLaunchEnabled: Boolean = false, // enable custom onboarding pages - // net.gini.android.capture.GiniCapture.Builder#setCustomOnboardingPages → on/off switch to show custom onboarding pages + // net.gini.android.capture.GiniCapture.Builder#setCustomOnboardingPages → + // on/off switch to show custom onboarding pages val isCustomOnboardingPagesEnabled: Boolean = false, // enable align corners in custom onboarding pages @@ -86,7 +92,8 @@ data class ExampleAppBankConfiguration( val isLightingInCustomOnboardingEnabled: Boolean = false, // enable QR code in custom onboarding pages - // net.gini.android.capture.GiniCapture.Builder#setOnboardingQRCodeIllustrationAdapter-> on/off switch to show custom adapter with animated illustrations + // net.gini.android.capture.GiniCapture.Builder#setOnboardingQRCodeIllustrationAdapter-> + // on/off switch to show custom adapter with animated illustrations val isQRCodeInCustomOnboardingEnabled: Boolean = false, // enable multi page in custom onboarding pages @@ -99,11 +106,13 @@ data class ExampleAppBankConfiguration( // enable button's custom loading indicator - // net.gini.android.capture.GiniCapture.Builder#setOnButtonLoadingIndicatorAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setOnButtonLoadingIndicatorAdapter → + // on/off switch to show a custom adapter implementation val isButtonsCustomLoadingIndicatorEnabled: Boolean = false, // enable screen's custom loading indicator - // net.gini.android.capture.GiniCapture.Builder#setLoadingIndicatorAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setLoadingIndicatorAdapter → + // on/off switch to show a custom adapter implementation val isScreenCustomLoadingIndicatorEnabled: Boolean = false, @@ -116,7 +125,8 @@ data class ExampleAppBankConfiguration( val isCustomHelpItemsEnabled: Boolean = false, // enable custom navigation bar - // net.gini.android.capture.GiniCapture.Builder#setNavigationBarTopAdapter → on/off switch to show a custom adapter implementation + // net.gini.android.capture.GiniCapture.Builder#setNavigationBarTopAdapter → + // on/off switch to show a custom adapter implementation val isCustomNavBarEnabled: Boolean = false, // enable custom primary button in compose @@ -161,6 +171,9 @@ data class ExampleAppBankConfiguration( // payment due hint threshold days val paymentDueHintThresholdDays: Int = GiniCapture.PAYMENT_DUE_HINT_THRESHOLD_DAYS, + // enable credit note hint + val isCreditNoteHintEnabled: Boolean = true, + // Digital invoice onboarding custom illustration val isDigitalInvoiceOnboardingCustomIllustrationEnabled: Boolean = false, @@ -219,6 +232,7 @@ data class ExampleAppBankConfiguration( isAlreadyPaidHintEnabled = defaultCaptureConfiguration.alreadyPaidHintEnabled, isPaymentDueHintEnabled = defaultCaptureConfiguration.paymentDueHintEnabled, paymentDueHintThresholdDays = defaultCaptureConfiguration.paymentDueHintThresholdDays, + isCreditNoteHintEnabled = defaultCaptureConfiguration.creditNoteHintEnabled, isOnboardingAtFirstRunEnabled = defaultCaptureConfiguration.showOnboardingAtFirstRun, isOnboardingAtEveryLaunchEnabled = defaultCaptureConfiguration.showOnboarding, isSupportedFormatsHelpScreenEnabled = defaultCaptureConfiguration.supportedFormatsHelpScreenEnabled, diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/util/CaptureResultListener.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/util/CaptureResultListener.kt index 3cb38446ba..605b4f8060 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/util/CaptureResultListener.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/util/CaptureResultListener.kt @@ -35,7 +35,8 @@ class CaptureResultListener(val context: Activity) : GiniCaptureFragmentListener is CaptureSDKResult.Error -> { Toast.makeText( context, - "Error: ${(result.value as ResultError.FileImport).code} ${(result.value as ResultError.FileImport).message}", + "Error: ${(result.value as ResultError.FileImport).code} " + + "${(result.value as ResultError.FileImport).message}", Toast.LENGTH_LONG ).show() @@ -54,4 +55,4 @@ class CaptureResultListener(val context: Activity) : GiniCaptureFragmentListener } } } -} \ No newline at end of file +} diff --git a/bank-sdk/example-app/src/main/res/layout/layout_feature_toggles.xml b/bank-sdk/example-app/src/main/res/layout/layout_feature_toggles.xml index 6fcb09ceb3..a802e0c1ce 100644 --- a/bank-sdk/example-app/src/main/res/layout/layout_feature_toggles.xml +++ b/bank-sdk/example-app/src/main/res/layout/layout_feature_toggles.xml @@ -204,5 +204,11 @@ tools:text="Hello@World and stuff and foo and bar and whatchamacallit" /> - + + 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 707972b722..7f2c8fd15c 100644 --- a/bank-sdk/example-app/src/main/res/values/strings.xml +++ b/bank-sdk/example-app/src/main/res/values/strings.xml @@ -62,6 +62,7 @@ Already Paid Hint Payment Due Hint Payment Due Hint Threshold Days + Credit Note Hint 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 b4377e36e4..83f4a2f2cc 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 @@ -142,7 +142,7 @@ data class CaptureConfiguration( val bottomNavigationBarEnabled: Boolean = false, /** - * Enable/disable the payment hint. + * Enable/disable the already paid hint. * * On by default. */ @@ -162,6 +162,13 @@ data class CaptureConfiguration( */ val paymentDueHintThresholdDays: Int = GiniCapture.PAYMENT_DUE_HINT_THRESHOLD_DAYS, + /** + * Enable/disable the credit note hint. + * + * On by default. + */ + val creditNoteHintEnabled: Boolean = true, + /** * Set an adapter implementation to show a custom bottom navigation bar on the onboarding screen. */ @@ -276,6 +283,7 @@ internal fun GiniCapture.Builder.applyConfiguration(configuration: CaptureConfig .setAlreadyPaidHintEnabled(configuration.alreadyPaidHintEnabled) .setPaymentDueHintEnabled(configuration.paymentDueHintEnabled) .setPaymentDueHintThresholdDays(configuration.paymentDueHintThresholdDays) + .setCreditNoteHintEnabled(configuration.creditNoteHintEnabled) .setEntryPoint(configuration.entryPoint) .setAllowScreenshots(configuration.allowScreenshots) .setSaveInvoicesLocallyEnabled(configuration.saveInvoicesLocallyEnabled) 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 a9835aa72e..5bb0ad540b 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 @@ -206,7 +206,8 @@ internal constructor( isPaymentDueHintEnabled = configuration.isPaymentDueHintEnabled, isEInvoiceEnabled = configuration.isEInvoiceEnabled, amplitudeApiKey = configuration.amplitudeApiKey ?: "", - isSavePhotosLocallyEnabled = configuration.isSavePhotosLocallyEnabled + isSavePhotosLocallyEnabled = configuration.isSavePhotosLocallyEnabled, + isCreditNoteHintEnabled = configuration.isCreditNoteHintEnabled, ) @Suppress("LongMethod") diff --git a/capture-sdk/default-network/src/test/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkServiceTest.kt b/capture-sdk/default-network/src/test/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkServiceTest.kt index c302391817..363c0ea4f7 100644 --- a/capture-sdk/default-network/src/test/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkServiceTest.kt +++ b/capture-sdk/default-network/src/test/java/net/gini/android/capture/network/GiniCaptureDefaultNetworkServiceTest.kt @@ -130,7 +130,8 @@ class GiniCaptureDefaultNetworkServiceTest { isQrCodeEducationEnabled = true, isSavePhotosLocallyEnabled = true, isAlreadyPaidHintEnabled = true, - isPaymentDueHintEnabled = true + isPaymentDueHintEnabled = true, + isCreditNoteHintEnabled = true ) // Add more stubs if mapBankConfigurationToConfiguration uses other properties diff --git a/capture-sdk/sdk/src/androidTest/java/net/gini/android/capture/ginicapture/GiniCaptureFragmentTest.kt b/capture-sdk/sdk/src/androidTest/java/net/gini/android/capture/ginicapture/GiniCaptureFragmentTest.kt index a6e36fb97c..7cf02668b1 100644 --- a/capture-sdk/sdk/src/androidTest/java/net/gini/android/capture/ginicapture/GiniCaptureFragmentTest.kt +++ b/capture-sdk/sdk/src/androidTest/java/net/gini/android/capture/ginicapture/GiniCaptureFragmentTest.kt @@ -196,7 +196,8 @@ class GiniCaptureFragmentTest { amplitudeApiKey = TEST_API_KEY, isSavePhotosLocallyEnabled = savePhotosLocallyEnabled, isPaymentDueHintEnabled = false, - isAlreadyPaidHintEnabled = false + isAlreadyPaidHintEnabled = false, + isCreditNoteHintEnabled = false ) return ConfigurationNetworkResult(testConfig, UUID.randomUUID()) 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 e6ba9decdd..ce4e9342a0 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 @@ -124,6 +124,7 @@ public class GiniCapture { private final boolean isAlreadyPaidHintEnabled; private final boolean isPaymentDueHintEnabled; private final int paymentDueHintThresholdDays; + private final boolean isCreditNoteHintEnabled; private final InjectedViewAdapterInstance onboardingAlignCornersIllustrationAdapterInstance; private final InjectedViewAdapterInstance onboardingLightingIllustrationAdapterInstance; private final InjectedViewAdapterInstance onboardingMultiPageIllustrationAdapterInstance; @@ -435,6 +436,7 @@ private GiniCapture(@NonNull final Builder builder) { isAlreadyPaidHintEnabled = builder.isAlreadyPaidHintEnabled(); isPaymentDueHintEnabled = builder.isPaymentDueHintEnabled(); paymentDueHintThresholdDays = builder.getPaymentDueHintThresholdDays(); + isCreditNoteHintEnabled = builder.isCreditNoteHintEnabled(); onboardingAlignCornersIllustrationAdapterInstance = builder.getOnboardingAlignCornersIllustrationAdapterInstance(); onboardingLightingIllustrationAdapterInstance = builder.getOnboardingLightingIllustrationAdapterInstance(); onboardingMultiPageIllustrationAdapterInstance = builder.getOnboardingMultiPageIllustrationAdapterInstance(); @@ -736,6 +738,10 @@ public int getPaymentDueHintThresholdDays() { return paymentDueHintThresholdDays; } + public boolean isCreditNoteHintEnabled() { + return isCreditNoteHintEnabled; + } + @Nullable public OnboardingIllustrationAdapter getOnboardingAlignCornersIllustrationAdapter() { if (onboardingAlignCornersIllustrationAdapterInstance == null) { @@ -942,6 +948,7 @@ public void onAnalysisScreenEvent(@NotNull final Event even private boolean isAlreadyPaidHintEnabled = true; private boolean isPaymentDueHintEnabled = true; private int paymentDueHintThresholdDays = PAYMENT_DUE_HINT_THRESHOLD_DAYS; + private boolean isCreditNoteHintEnabled = true; private InjectedViewAdapterInstance onboardingAlignCornersIllustrationAdapterInstance; private InjectedViewAdapterInstance onboardingLightingIllustrationAdapterInstance; private InjectedViewAdapterInstance onboardingMultiPageIllustrationAdapterInstance; @@ -1378,6 +1385,11 @@ public Builder setPaymentDueHintThresholdDays(final int thresholdDays){ return this; } + public Builder setCreditNoteHintEnabled(final Boolean enabled){ + isCreditNoteHintEnabled = enabled; + return this; + } + private boolean isBottomNavigationBarEnabled() { return isBottomNavigationBarEnabled; } @@ -1394,6 +1406,10 @@ private int getPaymentDueHintThresholdDays(){ return paymentDueHintThresholdDays; } + private boolean isCreditNoteHintEnabled(){ + return isCreditNoteHintEnabled; + } + @NonNull private InjectedViewAdapterInstance getOnboardingAlignCornersIllustrationAdapterInstance() { return onboardingAlignCornersIllustrationAdapterInstance; 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 5f7f6732e8..cc81e405f3 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 @@ -1,8 +1,5 @@ package net.gini.android.capture.analysis; -import static net.gini.android.capture.tracking.EventTrackingHelper.trackAnalysisScreenEvent; -import static net.gini.android.capture.util.SharedPreferenceHelper.SAF_STORAGE_URI_KEY; - import android.app.Activity; import android.content.Context; import android.content.DialogInterface; @@ -68,6 +65,7 @@ import kotlin.Unit; import static net.gini.android.capture.tracking.EventTrackingHelper.trackAnalysisScreenEvent; +import static net.gini.android.capture.util.SharedPreferenceHelper.SAF_STORAGE_URI_KEY; /** * Main logic implementation for analysis UI presented by {@link AnalysisFragment} @@ -352,6 +350,11 @@ void showAlreadyPaidWarning(@NonNull WarningType warningType, @NonNull Runnable mFragment.showWarning(warningType, onProceed); } + @Override + void showCreditNoteWarning(@NonNull WarningType warningType, @NonNull Runnable onProceed) { + mFragment.showWarning(warningType, onProceed); + } + @Override void showPaymentDueHint(PaymentDueHintDismissListener listener, String dueDate) { fragmentExtension.showPaymentDueHint(() -> { 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 da550cf7e8..60fc2460df 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 @@ -67,6 +67,7 @@ abstract void showAlertDialog(@NonNull final String message, abstract void showError(String errorMessage, Document document); abstract void showAlreadyPaidWarning(@NonNull WarningType warningType, @NonNull Runnable onProceed); + abstract void showCreditNoteWarning(@NonNull WarningType warningType, @NonNull Runnable onProceed); abstract void showPaymentDueHint(PaymentDueHintDismissListener listener, String dueDate); abstract void showError(ErrorType errorType, Document document); 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 c2c1171b75..b3bf8b2136 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 @@ -12,6 +12,7 @@ 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.BusinessDocType; import net.gini.android.capture.analysis.warning.WarningPaymentState; import net.gini.android.capture.document.DocumentFactory; import net.gini.android.capture.document.GiniCaptureDocument; @@ -38,6 +39,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -66,6 +68,7 @@ class AnalysisScreenPresenter extends AnalysisScreenContract.Presenter { private static final String EXTRACTION_PAYMENT_STATE = "paymentState"; private static final String EXTRACTION_PAYMENT_DUE_DATE = "paymentDueDate"; + private static final String EXTRACTION_BUSINESS_DOC_TYPE = "businessDocType"; private static final Logger LOG = LoggerFactory.getLogger(AnalysisScreenPresenter.class); @@ -349,6 +352,14 @@ public Void apply(final AnalysisInteractor.ResultHolder resultHolder, isSavingInvoicesInProgress, successResultHolder, getActivity()); + } else if (shouldShowCreditNoteWarning(resultHolder)) { + successResultHolder = removeAmountToPay(resultHolder); + shouldClearImageCaches = false; + extension.showCreditNoteHint( + mIsInvoiceSavingEnabled, + isSavingInvoicesInProgress, + successResultHolder, + getActivity()); } else if (shouldShowPaymentDueHint(resultHolder)) { successResultHolder = resultHolder; shouldClearImageCaches = false; @@ -573,6 +584,22 @@ private boolean shouldShowAlreadyPaidInvoiceWarning( return state.isPaid(); } + private boolean shouldShowCreditNoteWarning( + @NonNull final AnalysisInteractor.ResultHolder resultHolder) { + // Feature flags / config + final boolean creditNoteHintClientFlagEnabled = extension.getCreditNoteHintEnabledUseCase().invoke(); + + final boolean creditNoteHintSDKFlag = GiniCapture.hasInstance() && GiniCapture.getInstance().isCreditNoteHintEnabled(); + + if (!creditNoteHintClientFlagEnabled || !creditNoteHintSDKFlag) { + return false; + } + + // business doc type + final BusinessDocType businessDocType = extractBusinessDocType(resultHolder.getExtractions()); + return businessDocType.isCreditNote(); + } + private boolean shouldShowPaymentDueHint( @NonNull final AnalysisInteractor.ResultHolder resultHolder) { @@ -633,4 +660,27 @@ private WarningPaymentState extractPaymentState( return WarningPaymentState.from(paymentStateValue); } + // extracts the business doc type from extractions + private BusinessDocType extractBusinessDocType( + @NonNull final Map extractions) { + final GiniCaptureSpecificExtraction businessDocTypeExtraction = extractions.get(EXTRACTION_BUSINESS_DOC_TYPE); + final String paymentStateValue = businessDocTypeExtraction != null ? businessDocTypeExtraction.getValue() : null; + return BusinessDocType.from(paymentStateValue); + } + + private AnalysisInteractor.ResultHolder removeAmountToPay(@NonNull final AnalysisInteractor.ResultHolder resultHolder) { + Map extractions = new HashMap<>(resultHolder.getExtractions()); + extractions.remove("amountToPay"); + return new AnalysisInteractor.ResultHolder( + resultHolder.getResult(), + extractions, + new HashMap<>(), + new ArrayList<>(), + resultHolder.getDocumentId(), + resultHolder.getDocumentFileName() + ); + + + } + } 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 fcdd354aa1..a91c043792 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 @@ -30,6 +30,7 @@ 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.GetAlreadyPaidHintEnabledUseCase +import net.gini.android.capture.paymentHints.GetCreditNoteHintEnabledUseCase import net.gini.android.capture.paymentHints.GetPaymentDueHintEnabledUseCase import net.gini.android.capture.tracking.AnalysisScreenEvent import net.gini.android.capture.tracking.EventTrackingHelper @@ -48,6 +49,9 @@ internal class AnalysisScreenPresenterExtension( val paymentDueHintEnabledUseCase: GetPaymentDueHintEnabledUseCase by getGiniCaptureKoin().inject() + val creditNoteHintEnabledUseCase: + GetCreditNoteHintEnabledUseCase by getGiniCaptureKoin().inject() + val lastAnalyzedDocumentProvider: LastAnalyzedDocumentProvider by getGiniCaptureKoin().inject() @@ -173,6 +177,36 @@ internal class AnalysisScreenPresenterExtension( } } + fun showCreditNoteHint( + mIsInvoiceSavingEnabled: Boolean, + isSavingInvoicesInProgress: Boolean, + resultHolder: ResultHolder, + activity: Activity + ) { + if (isSavingInvoicesInProgress) { + handleSaveInvoicesLocally( + mIsInvoiceSavingEnabled, + true, + resultHolder, + activity + ) + } else { + doWhenEducationFinished { + view.showCreditNoteWarning( + WarningType.DOCUMENT_MARKED_AS_CREDIT_NOTE + ) { + handleSaveInvoicesLocally( + mIsInvoiceSavingEnabled, + false, + resultHolder, + activity + ) + } + } + } + } + + private fun handleSaveInvoicesLocally( mIsInvoiceSavingEnabled: Boolean, isSavingInvoicesInProgress: Boolean, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/BusinessDocType.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/BusinessDocType.kt new file mode 100644 index 0000000000..f811667f63 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/BusinessDocType.kt @@ -0,0 +1,33 @@ +package net.gini.android.capture.analysis.warning + +/** + * Represents the type of a business document, such as a credit note. + * + * Responsibilities: + * - Encapsulates the supported business document types relevant to the app + * (CREDIT_NOTE, UNKNOWN). + * - Provides a safe conversion from raw string values (via [from]) into one of the enum constants, + * handling nulls and unexpected values gracefully. + * - Offers convenience methods (e.g. [isCreditNote]) to simplify conditional checks in presenters or views. + */ +enum class BusinessDocType { + CREDIT_NOTE, UNKNOWN; + + companion object { + @JvmStatic + fun from(raw: String?): BusinessDocType { + return when (raw?.trim()) { + "CreditNote" -> CREDIT_NOTE + else -> UNKNOWN + } + } + } + + /** + * Checks if the business doc type indicates that the document is a credit note. + * + * @return true if the business doc type is [CREDIT_NOTE], false otherwise. + */ + fun isCreditNote(): Boolean = this == CREDIT_NOTE + +} 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 0b24ee10cd..932b4cc3ba 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 @@ -39,6 +39,7 @@ class WarningBottomSheet : BottomSheetDialogFragment() { private var titleText: CharSequence? = null private var descText: CharSequence? = null + private var icon: Int? = null var listener: Listener? = null @@ -54,6 +55,7 @@ class WarningBottomSheet : BottomSheetDialogFragment() { if (type != null) { titleText = getString(type.titleRes) descText = getString(type.descriptionRes) + icon = type.iconRes } } @@ -135,9 +137,11 @@ class WarningBottomSheet : BottomSheetDialogFragment() { BottomSheetBehavior.STATE_COLLAPSED -> { state = BottomSheetBehavior.STATE_EXPANDED } + BottomSheetBehavior.STATE_HIDDEN -> { state = BottomSheetBehavior.STATE_EXPANDED } + else -> Unit } } @@ -149,11 +153,13 @@ class WarningBottomSheet : BottomSheetDialogFragment() { } } } + override fun onStart() { super.onStart() if (!resources.getBoolean(R.bool.gc_is_tablet) && - resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + ) { val bottomSheet = (dialog as? BottomSheetDialog) ?.findViewById(com.google.android.material.R.id.design_bottom_sheet) @@ -171,26 +177,28 @@ class WarningBottomSheet : BottomSheetDialogFragment() { } private fun bindUi() { - binding.warningTitle.text = titleText - binding.warningDescription.text = descText + binding.gcWarningTitleWarningBottomSheet.text = titleText + binding.gcWarningDescriptionWarningBottomSheet.text = descText + icon?.let { binding.gcWarningIconWarningBottomSheet?.setBackgroundResource(it) } - binding.warningIcon?.contentDescription = getString(R.string.gc_warning_icon_content_description) - binding.warningIcon?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES + binding.gcWarningIconWarningBottomSheet?.contentDescription = + getString(R.string.gc_warning_icon_content_description) + binding.gcWarningIconWarningBottomSheet?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES - binding.cancelButton.setOnClickListener { + binding.gcCancelButtonWarningBottomSheet.setOnClickListener { listener?.onCancelAction() dismissAllowingStateLoss() } - binding.proceedButton.setOnClickListener { + binding.gcProceedButtonWarningBottomSheet.setOnClickListener { listener?.onProceedAction() dismissAllowingStateLoss() } } - companion object { 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/WarningType.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/warning/WarningType.java index 3cb50ccc2c..3838b712d8 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 @@ -1,5 +1,6 @@ package net.gini.android.capture.analysis.warning; +import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; import net.gini.android.capture.R; @@ -13,16 +14,36 @@ public enum WarningType { DOCUMENT_MARKED_AS_PAID( R.string.gc_document_marked_paid_title, - R.string.gc_document_marked_paid_desc + R.string.gc_document_marked_paid_desc, + R.drawable.gc_bg_warning_circle + ), DOCUMENT_MARKED_AS_CREDIT_NOTE( + R.string.gc_document_marked_credit_note_title, + R.string.gc_document_marked_credit_note_desc, + R.drawable.gc_warning_icon ); - @StringRes private final int titleRes; - @StringRes private final int descriptionRes; + @StringRes + private final int titleRes; + @StringRes + private final int descriptionRes; + @DrawableRes + private final int iconRes; - WarningType(@StringRes int titleRes, @StringRes int descriptionRes) { + WarningType(@StringRes int titleRes, @StringRes int descriptionRes, @DrawableRes int iconRes) { this.titleRes = titleRes; this.descriptionRes = descriptionRes; + this.iconRes = iconRes; + } + + public int getTitleRes() { + return titleRes; + } + + public int getDescriptionRes() { + return descriptionRes; + } + + public int getIconRes() { + return iconRes; } - public int getTitleRes() { return titleRes; } - public int getDescriptionRes() { return descriptionRes; } } 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 index 247bc46f59..269c69e42a 100644 --- 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 @@ -1,6 +1,7 @@ package net.gini.android.capture.di import net.gini.android.capture.paymentHints.GetAlreadyPaidHintEnabledUseCase +import net.gini.android.capture.paymentHints.GetCreditNoteHintEnabledUseCase import net.gini.android.capture.paymentHints.GetPaymentDueHintEnabledUseCase import org.koin.dsl.module @@ -18,4 +19,10 @@ internal val paymentHintsModule = module { ) } + factory { + GetCreditNoteHintEnabledUseCase( + 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 f50fc6ddc2..2c1448c7e4 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 @@ -16,4 +16,5 @@ data class Configuration( val isSavePhotosLocallyEnabled: Boolean, val isAlreadyPaidHintEnabled: Boolean, val isPaymentDueHintEnabled: Boolean, + val isCreditNoteHintEnabled: 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 1ebd784831..4b36cd8bfe 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 @@ -16,7 +16,8 @@ class GiniBankConfigurationProvider { isEInvoiceEnabled = false, isSavePhotosLocallyEnabled = false, isAlreadyPaidHintEnabled = false, - isPaymentDueHintEnabled = false + isPaymentDueHintEnabled = false, + isCreditNoteHintEnabled = false, ) fun provide(): Configuration = configuration diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetCreditNoteHintEnabledUseCase.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetCreditNoteHintEnabledUseCase.kt new file mode 100644 index 0000000000..a5169b3c5b --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/paymentHints/GetCreditNoteHintEnabledUseCase.kt @@ -0,0 +1,10 @@ +package net.gini.android.capture.paymentHints + +import net.gini.android.capture.internal.provider.GiniBankConfigurationProvider + +class GetCreditNoteHintEnabledUseCase( + private val giniBankConfigurationProvider: GiniBankConfigurationProvider, +) { + operator fun invoke() = giniBankConfigurationProvider.provide().isCreditNoteHintEnabled + +} diff --git a/capture-sdk/sdk/src/main/res/drawable/gc_warning_icon.xml b/capture-sdk/sdk/src/main/res/drawable/gc_warning_icon.xml new file mode 100644 index 0000000000..d5dfce48ee --- /dev/null +++ b/capture-sdk/sdk/src/main/res/drawable/gc_warning_icon.xml @@ -0,0 +1,13 @@ + + + + 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 c1add9286e..ccac664efb 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 @@ -27,7 +27,7 @@ android:padding="@dimen/gc_large"> + android:layout_marginStart="@dimen/gc_large" + android:layout_marginEnd="@dimen/gc_small" + android:text="@string/gc_proceed_anyway" /> + android:layout_marginStart="@dimen/gc_small" + android:text="@string/gc_cancel_transfer" /> + + 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 4f56661011..0958a0d9fe 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 @@ -15,7 +15,7 @@ android:padding="@dimen/gc_large"> + app:layout_constraintEnd_toEndOf="@+id/gc_textScroll_warningBottomSheet" + app:layout_constraintStart_toStartOf="@+id/gc_textScroll_warningBottomSheet" + app:layout_constraintTop_toBottomOf="@+id/gc_warningIcon_warningBottomSheet"> + app:layout_constraintEnd_toEndOf="@+id/gc_textScroll_warningBottomSheet" + app:layout_constraintStart_toStartOf="@+id/gc_textScroll_warningBottomSheet" + app:layout_constraintTop_toBottomOf="@+id/gc_warningIcon_warningBottomSheet"> + /> @@ -49,10 +48,10 @@ android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/warningIcon"> + app:layout_constraintTop_toBottomOf="@+id/gc_warningIcon_warningBottomSheet"> + android:layout_marginTop="@dimen/gc_medium" + android:text="@string/gc_proceed_anyway" /> + android:text="@string/gc_cancel_transfer" /> + 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 c61e242bf9..9157c33812 100644 --- a/capture-sdk/sdk/src/main/res/values-en/strings.xml +++ b/capture-sdk/sdk/src/main/res/values-en/strings.xml @@ -180,10 +180,12 @@ - Cancel Transfer - Proceed Anyway + 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. + Credit Note Warning + It seems you submitted a credit note instead of a regular invoice. You might want to check your document mentioning “Gutschrift” or if the amount is negative. Warning Dialog Warning icon diff --git a/capture-sdk/sdk/src/main/res/values/strings.xml b/capture-sdk/sdk/src/main/res/values/strings.xml index 482e8d038f..fc69352f8f 100644 --- a/capture-sdk/sdk/src/main/res/values/strings.xml +++ b/capture-sdk/sdk/src/main/res/values/strings.xml @@ -198,12 +198,14 @@ analyse der Rechnung läuft - Überweisung Abbrechen - Trotzdem Fortfahren + Ü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. + Achtung: Gutschrift hochgeladen + Anscheinend haben Sie eine Gutschrift statt einer Rechnung hochgeladen. Bitte überprüfen Sie Ihr Dokument auf den Begriff „Gutschrift“ oder ob der Betrag negativ ist. Warndialog Warnsymbol