From 23db536249a62045a5bac80a67118d8af3bcbd0f Mon Sep 17 00:00:00 2001 From: ndubkov-distcotech Date: Thu, 25 Jul 2024 11:18:20 +0200 Subject: [PATCH] feat(bank-sdk): Skonto screen. Bottom bar redesign (#504) feat(bank-sdk): Skonto screen. Bottom bar redesign PP-648 --- Gemfile.lock | 221 ---------------- .../bank/sdk/exampleapp/ui/MainActivity.kt | 2 +- .../CustomSkontoNavigationBarBottomAdapter.kt | 4 - .../layout/custom_skonto_navigation_bar.xml | 9 +- bank-sdk/sdk/build.gradle.kts | 1 + .../bank/sdk/capture/CaptureFlowFragment.kt | 31 ++- .../DigitalInvoiceFragmentListener.kt | 7 +- .../sdk/capture/skonto/SkontoDataExtractor.kt | 61 ++++- .../bank/sdk/capture/skonto/SkontoFragment.kt | 244 +++++++++--------- .../capture/skonto/SkontoFragmentContract.kt | 14 +- .../capture/skonto/SkontoFragmentListener.kt | 24 ++ .../capture/skonto/SkontoFragmentViewModel.kt | 73 ++++-- .../SkontoNavigationBarBottomAdapter.kt | 7 - .../section/SkontoFooterSectionColors.kt | 3 + .../src/main/res/navigation/gbs_nav_graph.xml | 11 +- .../sdk/src/main/res/values-en/strings.xml | 1 + bank-sdk/sdk/src/main/res/values/strings.xml | 1 + .../analysis/AnalysisScreenPresenter.java | 1 + .../picker/date/GiniDatePickerDialog.kt | 6 +- .../DecimalInputVisualTransformation.kt | 7 +- .../textinput/amount/GiniAmountTextInput.kt | 14 +- .../capture/ui/theme/modifier/Width.kt | 30 +++ .../ui/theme/typography/GiniTypography.kt | 2 + .../sdk/src/main/res/values-en/strings.xml | 4 + .../sdk/src/main/res/values/strings.xml | 4 + gradle/libs.versions.toml | 2 + 26 files changed, 375 insertions(+), 409 deletions(-) delete mode 100644 Gemfile.lock create mode 100644 bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentListener.kt create mode 100644 capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/modifier/Width.kt diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index e6d2cb3dba..0000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,221 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.17) - atomos (0.1.3) - aws-eventstream (1.3.0) - aws-partitions (1.940.0) - aws-sdk-core (3.197.0) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.83.0) - aws-sdk-core (~> 3, >= 3.197.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.0) - aws-sdk-core (~> 3, >= 3.197.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - base64 (0.2.0) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.110.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.220.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored (~> 1.2) - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-env (>= 1.6.0, < 2.0.0) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - http-cookie (~> 1.0.5) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (>= 0.1.1, < 1.0.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.5) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (~> 3) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.54.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.3) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.31.0) - google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.7.0) - google-cloud-env (>= 1.0, < 3.a) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.4.0) - google-cloud-storage (1.47.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.6) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.7.2) - jwt (2.8.1) - base64 - mini_magick (4.12.0) - mini_mime (1.1.5) - multi_json (1.15.0) - multipart-post (2.4.1) - nanaimo (0.3.0) - naturally (2.2.1) - nkf (0.2.0) - optparse (0.5.0) - os (1.1.4) - plist (3.7.1) - public_suffix (5.0.5) - rake (13.2.1) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.8) - strscan (>= 3.0.9) - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.5) - signet (0.19.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - strscan (3.1.0) - terminal-notifier (2.0.0) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.2) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unicode-display_width (2.5.0) - word_wrap (1.0.0) - xcodeproj (1.24.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - arm64-darwin-21 - x86_64-darwin-20 - x86_64-linux - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.2.29 diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/MainActivity.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/MainActivity.kt index 3857d3c798..e27f2c63ca 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/MainActivity.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/MainActivity.kt @@ -73,7 +73,7 @@ class MainActivity : AppCompatActivity() { cancellationToken?.cancel() } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) if (intent != null && isIntentActionViewOrSend(intent)) { startGiniBankSdkForOpenWith(intent) diff --git a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/adapters/CustomSkontoNavigationBarBottomAdapter.kt b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/adapters/CustomSkontoNavigationBarBottomAdapter.kt index 0cba099059..6c3d673b39 100644 --- a/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/adapters/CustomSkontoNavigationBarBottomAdapter.kt +++ b/bank-sdk/example-app/src/main/java/net/gini/android/bank/sdk/exampleapp/ui/adapters/CustomSkontoNavigationBarBottomAdapter.kt @@ -11,10 +11,6 @@ class CustomSkontoNavigationBarBottomAdapter : SkontoNavigationBarBottomAdapter private var binding: CustomSkontoNavigationBarBinding? = null - override fun setOnHelpClickListener(onClick: () -> Unit) { - binding?.gbsHelpBtn?.setOnClickListener { onClick() } - } - override fun setOnBackClickListener(onClick: () -> Unit) { binding?.gbsBackBtn?.setOnClickListener { onClick() } } diff --git a/bank-sdk/example-app/src/main/res/layout/custom_skonto_navigation_bar.xml b/bank-sdk/example-app/src/main/res/layout/custom_skonto_navigation_bar.xml index ff2aa48516..e4fe93749a 100644 --- a/bank-sdk/example-app/src/main/res/layout/custom_skonto_navigation_bar.xml +++ b/bank-sdk/example-app/src/main/res/layout/custom_skonto_navigation_bar.xml @@ -69,14 +69,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/gc_medium" - android:padding="@dimen/gc_medium" - app:tint="?attr/colorOnBackground" - android:contentDescription="@string/gbs_digital_invoice_help_info" android:background="@android:color/transparent" + android:contentDescription="@string/gbs_digital_invoice_help_info" + android:padding="@dimen/gc_medium" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@+id/gbs_pay" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/gbs_pay" - app:srcCompat="@drawable/gbs_menu_question_circle_info" /> + app:srcCompat="@drawable/gbs_menu_question_circle_info" + app:tint="?attr/colorOnBackground" /> , + compoundExtractions: Map + ) { + didFinishWithResult = true + captureFlowFragmentListener.onFinishedWithResult( + CaptureResult.Success( + specificExtractions, + compoundExtractions, + emptyList() + ) + ) + } + + override fun onCancelFlow() { val popBackStack = navController.popBackStack() if (!popBackStack) { @@ -242,6 +259,7 @@ class CaptureFlowFragmentFactory( private val giniCaptureFragmentListener: GiniCaptureFragmentListener, private val openWithDocument: Document? = null, private val digitalInvoiceListener: DigitalInvoiceFragmentListener, + private val skontoListener: SkontoFragmentListener, private val cancelCallback: CancelListener ) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { @@ -258,6 +276,11 @@ class CaptureFlowFragmentFactory( cancelListener = cancelCallback } + SkontoFragment::class.java.name -> SkontoFragment().apply { + skontoFragmentListener = skontoListener + cancelListener = cancelCallback + } + else -> super.instantiate(classLoader, className) } } diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/digitalinvoice/DigitalInvoiceFragmentListener.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/digitalinvoice/DigitalInvoiceFragmentListener.kt index c533ebb92e..2b3ac4f483 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/digitalinvoice/DigitalInvoiceFragmentListener.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/digitalinvoice/DigitalInvoiceFragmentListener.kt @@ -2,7 +2,6 @@ package net.gini.android.bank.sdk.capture.digitalinvoice import net.gini.android.capture.network.model.GiniCaptureCompoundExtraction import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction -import net.gini.android.bank.sdk.capture.digitalinvoice.onboarding.DigitalInvoiceOnboardingFragment /** * Created by Alpar Szotyori on 05.12.2019. @@ -25,6 +24,8 @@ interface DigitalInvoiceFragmentListener { * @param specificExtractions - extractions like the "amountToPay", "iban", etc. * @param compoundExtractions - extractions like the "lineItems" */ - fun onPayInvoice(specificExtractions: Map, - compoundExtractions: Map) + fun onPayInvoice( + specificExtractions: Map, + compoundExtractions: Map + ) } \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoDataExtractor.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoDataExtractor.kt index caa6fdf2c6..c0eb3d0558 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoDataExtractor.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoDataExtractor.kt @@ -4,7 +4,6 @@ import net.gini.android.bank.sdk.capture.skonto.model.SkontoData import net.gini.android.bank.sdk.capture.skonto.model.SkontoData.Amount import net.gini.android.bank.sdk.capture.skonto.model.SkontoData.SkontoPaymentMethod import net.gini.android.capture.network.model.GiniCaptureCompoundExtraction -import net.gini.android.capture.network.model.GiniCaptureExtraction import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction import java.math.BigDecimal import java.time.LocalDate @@ -12,12 +11,64 @@ import java.time.LocalDate internal class SkontoDataExtractor { + companion object { + private var _extractions: MutableMap = extractions + val extractions + get() = _extractions + + private var _compoundExtractions: MutableMap = + compoundExtractions + val compoundExtractions + get() = _compoundExtractions + + + fun updateGiniExtractions(updatedData: SkontoFragmentContract.State.Ready) { + _extractions["amountToPay"]?.value = updatedData.totalAmount.amount.toString() + + val skontoDiscountMaps = compoundExtractions["skontoDiscounts"]?.specificExtractionMaps + skontoDiscountMaps?.map { skontoDiscountData -> + skontoDiscountData.putDataByKeys( + updatedData.skontoPercentage.toString(), + "skontoPercentageDiscounted", + "skontoPercentageDiscountedCalculated", + ) ?: throw NoSuchElementException("Data for `PercentageDiscounted` is missing") + + skontoDiscountData.putDataByKeys( + updatedData.skontoAmount.amount.toString(), + "skontoAmountToPay", + "skontoAmountToPayCalculated" + ) + + skontoDiscountData.putDataByKeys( + updatedData.paymentInDays.toString(), + "skontoRemainingDays", + "skontoRemainingDaysCalculated" + ) + + skontoDiscountData.putDataByKeys( + updatedData.discountDueDate.toString(), + "skontoDueDate", + "skontoDueDateCalculated" + ) + + skontoDiscountData.putDataByKeys( + updatedData.skontoPercentage.toString(), + "skontoAmountDiscounted", + "skontoAmountDiscountedCalculated" + ) + } + } + fun extractSkontoData( - extractions: Map, + extractions: Map, compoundExtractions: Map, ): SkontoData { + + _extractions = extractions.toMutableMap() + _compoundExtractions = compoundExtractions.toMutableMap() + val totalAmountToPay = extractions["amountToPay"] ?: throw NoSuchElementException("Field `extractions.amountToPay` is missing") @@ -70,3 +121,9 @@ internal class SkontoDataExtractor { fun Map.extractDataByKeys(vararg keys: String) = keys.firstNotNullOfOrNull { this[it] } + +fun MutableMap.putDataByKeys( + value: String, + vararg keys: String +) = + keys.firstNotNullOfOrNull { this[it]?.value = value } diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragment.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragment.kt index d8e7946bec..c9cf3127a2 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragment.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragment.kt @@ -4,12 +4,12 @@ package net.gini.android.bank.sdk.capture.skonto import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.icu.util.Calendar -import android.icu.util.TimeUnit import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background @@ -51,15 +51,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -71,6 +67,7 @@ import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import net.gini.android.bank.sdk.GiniBank import net.gini.android.bank.sdk.R @@ -83,6 +80,7 @@ import net.gini.android.bank.sdk.capture.skonto.colors.section.WithoutSkontoSect import net.gini.android.bank.sdk.capture.skonto.model.SkontoData import net.gini.android.bank.sdk.capture.util.currencyFormatterWithoutSymbol import net.gini.android.capture.GiniCapture +import net.gini.android.capture.internal.util.CancelListener import net.gini.android.capture.ui.components.button.filled.GiniButton import net.gini.android.capture.ui.components.picker.date.GiniDatePickerDialog import net.gini.android.capture.ui.components.switcher.GiniSwitch @@ -91,21 +89,24 @@ import net.gini.android.capture.ui.components.textinput.amount.GiniAmountTextInp import net.gini.android.capture.ui.components.topbar.GiniTopBar import net.gini.android.capture.ui.components.topbar.GiniTopBarColors import net.gini.android.capture.ui.theme.GiniTheme +import net.gini.android.capture.ui.theme.modifier.tabletMaxWidth +import net.gini.android.capture.ui.theme.typography.bold import net.gini.android.capture.view.InjectedViewAdapterInstance import java.math.BigDecimal import java.math.RoundingMode -import java.time.Instant import java.time.LocalDate -import java.time.LocalTime -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.chrono.ChronoLocalDate import java.time.format.DateTimeFormatter class SkontoFragment : Fragment() { - val args: SkontoFragmentArgs by navArgs() + private val args: SkontoFragmentArgs by navArgs() + + lateinit var cancelListener: CancelListener + + var skontoFragmentListener: SkontoFragmentListener? = null + set(value) { + field = value + } private val isBottomNavigationBarEnabled = GiniCapture.getInstance().isBottomNavigationBarEnabled @@ -123,9 +124,11 @@ class SkontoFragment : Fragment() { val viewModel = ViewModelProvider( factory = ViewModelFactory(args.data), - owner = requireActivity() + owner = this )[SkontoFragmentViewModel::class.java] + viewModel.setListener(skontoFragmentListener) + return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { @@ -134,6 +137,10 @@ class SkontoFragment : Fragment() { viewModel = viewModel, isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, customBottomNavBarAdapter = customBottomNavBarAdapter, + navigateBack = { + findNavController() + .navigate(SkontoFragmentDirections.toCaptureFragment()) + } ) } } @@ -153,12 +160,16 @@ class SkontoFragment : Fragment() { @Composable private fun ScreenContent( + navigateBack: () -> Unit, viewModel: SkontoFragmentViewModel, modifier: Modifier = Modifier, screenColorScheme: SkontoScreenColors = SkontoScreenColors.colors(), isBottomNavigationBarEnabled: Boolean, customBottomNavBarAdapter: InjectedViewAdapterInstance?, ) { + + BackHandler { navigateBack() } + val state by viewModel.stateFlow.collectAsState() ScreenStateContent( modifier = modifier, @@ -169,10 +180,9 @@ private fun ScreenContent( onDueDateChanged = viewModel::onSkontoDueDateChanged, onFullAmountChange = viewModel::onFullAmountFieldChanged, isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, - onBackClicked = {}, - onHelpClicked = {}, + onBackClicked = navigateBack, customBottomNavBarAdapter = customBottomNavBarAdapter, - onProceedClicked = {}, + onProceedClicked = { viewModel.onProceedClicked() }, onInfoBannerClicked = viewModel::onInfoBannerClicked, onInfoDialogDismissed = viewModel::onInfoDialogDismissed ) @@ -186,7 +196,6 @@ private fun ScreenStateContent( onFullAmountChange: (BigDecimal) -> Unit, onDueDateChanged: (LocalDate) -> Unit, onBackClicked: () -> Unit, - onHelpClicked: () -> Unit, onProceedClicked: () -> Unit, isBottomNavigationBarEnabled: Boolean, customBottomNavBarAdapter: InjectedViewAdapterInstance?, @@ -205,7 +214,6 @@ private fun ScreenStateContent( onDueDateChanged = onDueDateChanged, onFullAmountChange = onFullAmountChange, onBackClicked = onBackClicked, - onHelpClicked = onHelpClicked, isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, customBottomNavBarAdapter = customBottomNavBarAdapter, onProceedClicked = onProceedClicked, @@ -219,7 +227,6 @@ private fun ScreenStateContent( @Composable private fun ScreenReadyState( onBackClicked: () -> Unit, - onHelpClicked: () -> Unit, onProceedClicked: () -> Unit, state: SkontoFragmentContract.State.Ready, onDiscountSectionActiveChange: (Boolean) -> Unit, @@ -242,52 +249,55 @@ private fun ScreenReadyState( isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, colors = screenColorScheme.topAppBarColors, onBackClicked = onBackClicked, - onHelpClicked = onHelpClicked, ) }, bottomBar = { FooterSection( colors = screenColorScheme.footerSectionColors, - discountValue = state.discountAmount, + discountValue = state.skontoPercentage, totalAmount = state.totalAmount, isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, onBackClicked = onBackClicked, - onHelpClicked = onHelpClicked, customBottomNavBarAdapter = customBottomNavBarAdapter, onProceedClicked = onProceedClicked, - isSkontoSectionActive = state.isSkontoSectionActive + isSkontoSectionActive = state.isSkontoSectionActive, + savedAmount = state.savedAmount ) }) { Column( modifier = Modifier .padding(it) - .verticalScroll(scrollState) + .verticalScroll(scrollState), + horizontalAlignment = Alignment.CenterHorizontally, ) { - YourInvoiceScanSection( - modifier = Modifier.padding(vertical = 8.dp), - colorScheme = screenColorScheme.invoiceScanSectionColors, - ) - SkontoSection( - modifier = Modifier.padding(vertical = 16.dp), - colors = screenColorScheme.skontoSectionColors, - amount = state.skontoAmount, - amountValidation = state.skontoAmountValidation, - dueDate = state.discountDueDate, - infoPaymentInDays = state.paymentInDays, - infoDiscountValue = state.discountAmount, - onActiveChange = onDiscountSectionActiveChange, - isActive = state.isSkontoSectionActive, - onSkontoAmountChange = onDiscountAmountChange, - onDueDateChanged = onDueDateChanged, - edgeCase = state.skontoEdgeCase, - onInfoBannerClicked = onInfoBannerClicked, - ) - WithoutSkontoSection( - colors = screenColorScheme.withoutSkontoSectionColors, - isActive = !state.isSkontoSectionActive, - amount = state.fullAmount, - onFullAmountChange = onFullAmountChange, - ) + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + SkontoSection( + modifier = Modifier + .padding(vertical = 16.dp) + .tabletMaxWidth(), + colors = screenColorScheme.skontoSectionColors, + amount = state.skontoAmount, + dueDate = state.discountDueDate, + infoPaymentInDays = state.paymentInDays, + infoDiscountValue = state.skontoPercentage, + onActiveChange = onDiscountSectionActiveChange, + isActive = state.isSkontoSectionActive, + onSkontoAmountChange = onDiscountAmountChange, + onDueDateChanged = onDueDateChanged, + edgeCase = state.skontoEdgeCase, + onInfoBannerClicked = onInfoBannerClicked, + ) + WithoutSkontoSection( + modifier = Modifier.tabletMaxWidth(), + colors = screenColorScheme.withoutSkontoSectionColors, + isActive = !state.isSkontoSectionActive, + amount = state.fullAmount, + onFullAmountChange = onFullAmountChange, + ) + } } if (state.edgeCaseInfoDialogVisible) { @@ -298,7 +308,7 @@ private fun ScreenReadyState( SkontoFragmentContract.SkontoEdgeCase.SkontoExpired -> stringResource( id = R.string.gbs_skonto_section_info_dialog_date_expired_message, - state.discountAmount.toFloat().formatAsDiscountPercentage() + state.skontoPercentage.toFloat().formatAsDiscountPercentage() ) SkontoFragmentContract.SkontoEdgeCase.SkontoLastDay -> @@ -320,7 +330,6 @@ private fun ScreenReadyState( @Composable private fun TopAppBar( onBackClicked: () -> Unit, - onHelpClicked: () -> Unit, modifier: Modifier = Modifier, isBottomNavigationBarEnabled: Boolean, colors: GiniTopBarColors, @@ -333,11 +342,6 @@ private fun TopAppBar( AnimatedVisibility(visible = !isBottomNavigationBarEnabled) { NavigationActionBack(onClick = onBackClicked) } - }, - actions = { - AnimatedVisibility(visible = !isBottomNavigationBarEnabled) { - NavigationActionHelp(onClick = onHelpClicked) - } }) } @@ -357,23 +361,6 @@ private fun NavigationActionBack( } } -@Composable -private fun NavigationActionHelp( - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - IconButton( - modifier = modifier, - onClick = onClick - ) { - Icon( - modifier = Modifier, - painter = painterResource(net.gini.android.capture.R.drawable.gc_help_icon), - contentDescription = null, - ) - } -} - @Composable private fun YourInvoiceScanSection( modifier: Modifier = Modifier, @@ -435,7 +422,6 @@ private fun YourInvoiceScanSection( private fun SkontoSection( colors: SkontoSectionColors, amount: SkontoData.Amount, - amountValidation: SkontoFragmentContract.State.Ready.SkontoAmountValidation, dueDate: LocalDate, infoPaymentInDays: Int, infoDiscountValue: BigDecimal, @@ -450,11 +436,8 @@ private fun SkontoSection( val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") var isDatePickerVisible by remember { mutableStateOf(false) } - val dueDateFieldFocusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - Card( - modifier = modifier.fillMaxWidth(), + modifier = modifier, shape = RectangleShape, colors = CardDefaults.cardColors(containerColor = colors.cardBackgroundColor) ) { @@ -533,11 +516,11 @@ private fun SkontoSection( ) GiniAmountTextInput( amount = amount.amount, + currencyCode = amount.currencyCode, modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), enabled = isActive, - isError = amountValidation != SkontoFragmentContract.State.Ready.SkontoAmountValidation.Valid, colors = colors.amountFieldColors, onValueChange = { onSkontoAmountChange(it) }, label = stringResource(id = R.string.gbs_skonto_section_discount_field_amount_hint), @@ -701,7 +684,7 @@ private fun WithoutSkontoSection( isActive: Boolean, ) { Card( - modifier = modifier.fillMaxWidth(), + modifier = modifier, shape = RectangleShape, colors = CardDefaults.cardColors(containerColor = colors.cardBackgroundColor) ) { @@ -734,6 +717,7 @@ private fun WithoutSkontoSection( enabled = isActive, colors = colors.amountFieldColors, amount = amount.amount, + currencyCode = amount.currencyCode, onValueChange = onFullAmountChange, label = stringResource(id = R.string.gbs_skonto_section_without_discount_field_amount_hint), trailingContent = { @@ -752,12 +736,12 @@ private fun WithoutSkontoSection( @Composable private fun FooterSection( totalAmount: SkontoData.Amount, + savedAmount: SkontoData.Amount, discountValue: BigDecimal, colors: SkontoFooterSectionColors, isBottomNavigationBarEnabled: Boolean, isSkontoSectionActive: Boolean, onBackClicked: () -> Unit, - onHelpClicked: () -> Unit, onProceedClicked: () -> Unit, modifier: Modifier = Modifier, customBottomNavBarAdapter: InjectedViewAdapterInstance?, @@ -765,11 +749,22 @@ private fun FooterSection( val animatedTotalAmount by animateFloatAsState( targetValue = totalAmount.amount.toFloat(), label = "totalAmount" ) + val animatedSavedAmount by animateFloatAsState( + targetValue = savedAmount.amount.toFloat(), label = "savedAmount" + ) val animatedDiscountAmount by animateFloatAsState( targetValue = discountValue.toFloat(), label = "discountAmount" ) val totalPriceText = - "${currencyFormatterWithoutSymbol().format(animatedTotalAmount)} ${totalAmount.currencyCode}" + "${ + currencyFormatterWithoutSymbol().format(animatedTotalAmount).trim() + } ${totalAmount.currencyCode}" + + val savedAmountText = + "${ + currencyFormatterWithoutSymbol().format(animatedSavedAmount).trim() + } ${savedAmount.currencyCode}" + val discountLabelText = stringResource( id = R.string.gbs_skonto_section_footer_label_discount, animatedDiscountAmount.formatAsDiscountPercentage() @@ -783,8 +778,7 @@ private fun FooterSection( }, update = { with(customBottomNavBarAdapter.viewAdapter) { setTotalPriceText(totalPriceText) - setProceedButtonEnabled(proceedEnabled) // TODO Integrate validation - setOnHelpClickListener(onHelpClicked) + setProceedButtonEnabled(proceedEnabled) setOnBackClickListener(onBackClicked) setDiscountLabelText(discountLabelText) setDiscountLabelVisible(isSkontoSectionActive) @@ -795,34 +789,32 @@ private fun FooterSection( Card( modifier = modifier.fillMaxWidth(), shape = RectangleShape, - colors = CardDefaults.cardColors(containerColor = colors.cardBackgroundColor) + colors = CardDefaults.cardColors(containerColor = colors.cardBackgroundColor), ) { - Column { + Column( + modifier = Modifier + .tabletMaxWidth() + .align(Alignment.CenterHorizontally), + horizontalAlignment = Alignment.CenterHorizontally + ) { Column( - modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp) + modifier = Modifier + .padding(start = 20.dp, end = 20.dp, top = 20.dp) ) { - Text( - text = stringResource(id = R.string.gbs_skonto_section_footer_title), - style = GiniTheme.typography.body1, - color = colors.titleTextColor, - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 4.dp), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - ) { + Row { Text( - text = totalPriceText, - style = GiniTheme.typography.headline5, - color = colors.amountTextColor, + modifier = Modifier.weight(0.1f), + text = stringResource(id = R.string.gbs_skonto_section_footer_title), + style = GiniTheme.typography.body1, + color = colors.titleTextColor, ) - AnimatedVisibility(visible = isSkontoSectionActive) { + AnimatedVisibility( + visible = isSkontoSectionActive + ) { Box( modifier = Modifier .height(IntrinsicSize.Min) - .padding(horizontal = 12.dp) + .padding(horizontal = 4.dp) .background( colors.discountLabelColorScheme.backgroundColor, RoundedCornerShape(4.dp) @@ -837,10 +829,35 @@ private fun FooterSection( } } } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = totalPriceText, + style = GiniTheme.typography.headline5.bold(), + color = colors.amountTextColor, + ) + } + AnimatedVisibility( + visible = isSkontoSectionActive + ) { + Text( + text = stringResource( + id = R.string.gbs_skonto_section_footer_label_save, + savedAmountText + ), + style = GiniTheme.typography.caption1, + color = colors.savedAmountTextColor, + ) + } } - val buttonPaddingHorizontal = if (isBottomNavigationBarEnabled) 0.dp else 20.dp + val buttonPaddingStart = if (isBottomNavigationBarEnabled) 0.dp else 20.dp + val buttonPaddingEnd = if (isBottomNavigationBarEnabled) 48.dp else 24.dp Row( - modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly ) { @@ -853,17 +870,11 @@ private fun FooterSection( GiniButton( modifier = Modifier .weight(0.1f) - .padding(horizontal = buttonPaddingHorizontal), + .padding(start = buttonPaddingStart, end = buttonPaddingEnd), text = stringResource(id = R.string.gbs_skonto_section_footer_continue_button_text), onClick = onProceedClicked, giniButtonColors = colors.continueButtonColors ) - AnimatedVisibility(visible = isBottomNavigationBarEnabled) { - NavigationActionHelp( - modifier = Modifier.padding(horizontal = 4.dp), - onClick = onHelpClicked - ) - } } } } @@ -895,9 +906,8 @@ private fun ScreenReadyStatePreview() { onDiscountAmountChange = {}, onDueDateChanged = {}, onFullAmountChange = {}, - onHelpClicked = {}, onBackClicked = {}, - isBottomNavigationBarEnabled = false, + isBottomNavigationBarEnabled = true, onProceedClicked = {}, customBottomNavBarAdapter = null, onInfoDialogDismissed = {}, @@ -914,7 +924,7 @@ private fun Float.formatAsDiscountPercentage(): String { private val previewState = SkontoFragmentContract.State.Ready( isSkontoSectionActive = true, paymentInDays = 14, - discountAmount = BigDecimal("3"), + skontoPercentage = BigDecimal("3"), skontoAmount = SkontoData.Amount(BigDecimal("97"), "EUR"), discountDueDate = LocalDate.now(), fullAmount = SkontoData.Amount(BigDecimal("100"), "EUR"), @@ -922,5 +932,5 @@ private val previewState = SkontoFragmentContract.State.Ready( paymentMethod = SkontoData.SkontoPaymentMethod.PayPal, skontoEdgeCase = SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly, edgeCaseInfoDialogVisible = false, - skontoAmountValidation = SkontoFragmentContract.State.Ready.SkontoAmountValidation.Invalid.SkontoAmountGreaterOfFullAmount, + savedAmount = SkontoData.Amount(BigDecimal("3"), "EUR") ) \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentContract.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentContract.kt index 0150215e28..a11620e306 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentContract.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentContract.kt @@ -10,24 +10,16 @@ internal object SkontoFragmentContract { data class Ready( val isSkontoSectionActive: Boolean, val paymentInDays: Int, - val discountAmount: BigDecimal, + val skontoPercentage: BigDecimal, val skontoAmount: SkontoData.Amount, - val skontoAmountValidation: SkontoAmountValidation, val discountDueDate: LocalDate, val fullAmount: SkontoData.Amount, val totalAmount: SkontoData.Amount, + val savedAmount: SkontoData.Amount, val paymentMethod: SkontoData.SkontoPaymentMethod, val skontoEdgeCase: SkontoEdgeCase?, val edgeCaseInfoDialogVisible: Boolean, - ) : State() { - sealed class SkontoAmountValidation { - object Valid : SkontoAmountValidation() - - sealed class Invalid : SkontoAmountValidation() { - object SkontoAmountGreaterOfFullAmount : Invalid() - } - } - } + ) : State() } sealed class SkontoEdgeCase { diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentListener.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentListener.kt new file mode 100644 index 0000000000..f5384049bc --- /dev/null +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentListener.kt @@ -0,0 +1,24 @@ +package net.gini.android.bank.sdk.capture.skonto + +import net.gini.android.capture.network.model.GiniCaptureCompoundExtraction +import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction + +/** + * Interface used by the [SkontoFragment] to dispatch events to the hosting Activity. + */ +interface SkontoFragmentListener { + + /** + * Called when the user presses the proceed button. + * + * The extractions were updated to contain the user's modifications: + * - "amountToPay" was updated to contain the new amount to pay (with or without skonto) + * + * @param specificExtractions - extractions like the "amountToPay", "iban", etc. + * @param compoundExtractions - extractions like the "skontoAmountToPay", "skontoDueDate", etc. + */ + fun onPayInvoiceWithSkonto( + specificExtractions: Map, + compoundExtractions: Map + ) +} \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentViewModel.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentViewModel.kt index 5ecab38e06..00ce624e99 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentViewModel.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoFragmentViewModel.kt @@ -18,6 +18,21 @@ internal class SkontoFragmentViewModel( val stateFlow: MutableStateFlow = MutableStateFlow(createInitalState(data)) + private var listener: SkontoFragmentListener? = null + + fun setListener(listener: SkontoFragmentListener?) { + this.listener = listener + } + + fun onProceedClicked() { + val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return + SkontoDataExtractor.updateGiniExtractions(currentState) + listener?.onPayInvoiceWithSkonto( + SkontoDataExtractor.extractions, + SkontoDataExtractor.compoundExtractions + ) + } + private fun createInitalState( data: SkontoData, ): SkontoFragmentContract.State.Ready { @@ -34,10 +49,14 @@ internal class SkontoFragmentViewModel( val totalAmount = if (isSkontoSectionActive) data.skontoAmountToPay else data.fullAmountToPay + val savedAmountValue = + calculateSavedAmount(data.skontoAmountToPay.amount, data.fullAmountToPay.amount) + val savedAmount = SkontoData.Amount(savedAmountValue, data.fullAmountToPay.currencyCode) + return SkontoFragmentContract.State.Ready( - isSkontoSectionActive = true, + isSkontoSectionActive = isSkontoSectionActive, paymentInDays = data.skontoRemainingDays, - discountAmount = discount, + skontoPercentage = discount, skontoAmount = data.skontoAmountToPay, discountDueDate = data.skontoDueDate, fullAmount = data.fullAmountToPay, @@ -45,10 +64,7 @@ internal class SkontoFragmentViewModel( paymentMethod = paymentMethod, skontoEdgeCase = edgeCase, edgeCaseInfoDialogVisible = edgeCase != null, - skontoAmountValidation = validateSkontoAmount( - skontoAmount = data.skontoAmountToPay, - fullAmount = data.fullAmountToPay - ) + savedAmount = savedAmount ) } @@ -62,16 +78,22 @@ internal class SkontoFragmentViewModel( currentState.copy( isSkontoSectionActive = newValue, totalAmount = totalAmount, - discountAmount = discount + skontoPercentage = discount ) ) } fun onSkontoAmountFieldChanged(newValue: BigDecimal) = viewModelScope.launch { val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return@launch - val discount = calculateDiscount(newValue, currentState.fullAmount.amount).coerceAtLeast( - BigDecimal.ZERO - ) + + if (newValue > currentState.fullAmount.amount) { + stateFlow.emit( + currentState.copy(skontoAmount = currentState.skontoAmount) + ) + return@launch + } + + val discount = calculateDiscount(newValue, currentState.fullAmount.amount) val totalAmount = if (currentState.isSkontoSectionActive) newValue else @@ -80,12 +102,16 @@ internal class SkontoFragmentViewModel( val newSkontoAmount = currentState.skontoAmount.copy(amount = newValue) val newTotalAmount = currentState.totalAmount.copy(amount = totalAmount) + val savedAmountValue = + calculateSavedAmount(newSkontoAmount.amount, currentState.fullAmount.amount) + val savedAmount = SkontoData.Amount(savedAmountValue, currentState.fullAmount.currencyCode) + stateFlow.emit( currentState.copy( skontoAmount = newSkontoAmount, - discountAmount = discount, + skontoPercentage = discount, totalAmount = newTotalAmount, - skontoAmountValidation = validateSkontoAmount(newSkontoAmount, currentState.fullAmount) + savedAmount = savedAmount, ) ) } @@ -110,7 +136,7 @@ internal class SkontoFragmentViewModel( val totalAmount = if (currentState.isSkontoSectionActive) currentState.skontoAmount.amount else newValue - val discount = currentState.discountAmount + val discount = currentState.skontoPercentage val skontoAmount = newValue.minus( newValue.multiply( // full_amount - (full_amount * (discount / 100)) @@ -118,11 +144,15 @@ internal class SkontoFragmentViewModel( ) ) + val savedAmountValue = calculateSavedAmount(skontoAmount, newValue) + val savedAmount = SkontoData.Amount(savedAmountValue, currentState.fullAmount.currencyCode) + stateFlow.emit( currentState.copy( skontoAmount = currentState.skontoAmount.copy(amount = skontoAmount), fullAmount = currentState.fullAmount.copy(amount = newValue), - totalAmount = currentState.totalAmount.copy(amount = totalAmount) + totalAmount = currentState.totalAmount.copy(amount = totalAmount), + savedAmount = savedAmount, ) ) } @@ -150,20 +180,11 @@ internal class SkontoFragmentViewModel( return BigDecimal.ONE .minus(skontoAmount.divide(fullAmount, 4, RoundingMode.HALF_UP)) .multiply(BigDecimal("100")) + .coerceAtLeast(BigDecimal.ZERO) } - private fun validateSkontoAmount( - skontoAmount: SkontoData.Amount, - fullAmount: SkontoData.Amount - ): SkontoFragmentContract.State.Ready.SkontoAmountValidation { - return when { - skontoAmount.amount <= fullAmount.amount -> - SkontoFragmentContract.State.Ready.SkontoAmountValidation.Valid - - else -> - SkontoFragmentContract.State.Ready.SkontoAmountValidation.Invalid.SkontoAmountGreaterOfFullAmount - } - } + private fun calculateSavedAmount(skontoAmount: BigDecimal, fullAmount: BigDecimal) = + fullAmount.minus(skontoAmount).coerceAtLeast(BigDecimal.ZERO) private fun extractSkontoEdgeCase( dueDate: LocalDate, diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoNavigationBarBottomAdapter.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoNavigationBarBottomAdapter.kt index 52a95e7b3c..50d1cc95cf 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoNavigationBarBottomAdapter.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/SkontoNavigationBarBottomAdapter.kt @@ -4,13 +4,6 @@ import net.gini.android.capture.view.InjectedViewAdapter interface SkontoNavigationBarBottomAdapter : InjectedViewAdapter { - /** - * Set the click listener for the help button. - * - * @param onClick the click function for the help button - */ - fun setOnHelpClickListener(onClick: () -> Unit) - /** * Set the click listener for the back button. * diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoFooterSectionColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoFooterSectionColors.kt index f565d4cf7b..12eec0d2f0 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoFooterSectionColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoFooterSectionColors.kt @@ -11,6 +11,7 @@ data class SkontoFooterSectionColors( val cardBackgroundColor: Color, val titleTextColor: Color, val amountTextColor: Color, + val savedAmountTextColor: Color, val discountLabelColorScheme: DiscountLabelColorScheme, val continueButtonColors: GiniButtonColors, ) { @@ -22,6 +23,7 @@ data class SkontoFooterSectionColors( cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, titleTextColor: Color = GiniTheme.colorScheme.text.primary, amountTextColor: Color = GiniTheme.colorScheme.text.primary, + savedAmountTextColor: Color = GiniTheme.colorScheme.text.success, discountLabelColorScheme: DiscountLabelColorScheme = DiscountLabelColorScheme.colors(), continueButtonColors: GiniButtonColors = GiniButtonColors.colors(), ) = SkontoFooterSectionColors( @@ -30,6 +32,7 @@ data class SkontoFooterSectionColors( amountTextColor = amountTextColor, discountLabelColorScheme = discountLabelColorScheme, continueButtonColors = continueButtonColors, + savedAmountTextColor = savedAmountTextColor, ) } diff --git a/bank-sdk/sdk/src/main/res/navigation/gbs_nav_graph.xml b/bank-sdk/sdk/src/main/res/navigation/gbs_nav_graph.xml index 6c9889d7c7..070dfa13aa 100644 --- a/bank-sdk/sdk/src/main/res/navigation/gbs_nav_graph.xml +++ b/bank-sdk/sdk/src/main/res/navigation/gbs_nav_graph.xml @@ -42,7 +42,7 @@ app:popEnterAnim="@anim/gc_nav_pop_enter_anim" app:popExitAnim="@anim/gc_nav_pop_exit_anim" app:popUpTo="@id/gbs_destination_capture_fragment" - app:popUpToInclusive="true" /> + app:popUpToInclusive="false" /> + \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/res/values-en/strings.xml b/bank-sdk/sdk/src/main/res/values-en/strings.xml index d55018d75f..06b2780240 100644 --- a/bank-sdk/sdk/src/main/res/values-en/strings.xml +++ b/bank-sdk/sdk/src/main/res/values-en/strings.xml @@ -64,6 +64,7 @@ Total %1$s Skonto discount + Save %1$s Continue to pay diff --git a/bank-sdk/sdk/src/main/res/values/strings.xml b/bank-sdk/sdk/src/main/res/values/strings.xml index d619b59793..d541c819a4 100644 --- a/bank-sdk/sdk/src/main/res/values/strings.xml +++ b/bank-sdk/sdk/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ Gesamtpreis %1$s Skonto + %1$s sparen Zahlung fortsetzen 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 689fe91682..d87bf53280 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 @@ -313,6 +313,7 @@ public Void apply(final AnalysisInteractor.ResultHolder resultHolder, .onProceedToNoExtractionsScreen(mMultiPageDocument); return null; } + getAnalysisFragmentListenerOrNoOp() .onExtractionsAvailable(getMapOrEmpty(resultHolder.getExtractions()), getMapOrEmpty(resultHolder.getCompoundExtractions()), diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialog.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialog.kt index 01e19ea034..308e7506d3 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialog.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialog.kt @@ -18,10 +18,12 @@ import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import net.gini.android.capture.R import net.gini.android.capture.ui.theme.GiniTheme import java.time.Instant import java.time.LocalDate @@ -79,7 +81,7 @@ fun GiniDatePickerDialog( modifier = Modifier.padding(horizontal = 8.dp), onClick = { onDismissRequest() }) { Text( - text = "Cancel", + text = stringResource(id = R.string.gc_date_picker_cancel), style = GiniTheme.typography.body1 ) } @@ -94,7 +96,7 @@ fun GiniDatePickerDialog( } }) { Text( - text = "Select", + text = stringResource(id = R.string.gc_date_picker_select), style = GiniTheme.typography.body1 ) } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/DecimalInputVisualTransformation.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/DecimalInputVisualTransformation.kt index 5cd42893d9..bd4e436547 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/DecimalInputVisualTransformation.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/DecimalInputVisualTransformation.kt @@ -7,12 +7,17 @@ import androidx.compose.ui.text.input.VisualTransformation import java.text.DecimalFormatSymbols class DecimalInputVisualTransformation( + private val currencyCode: String, + private val isCurrencyCodeDisplay: Boolean, private val decimalFormatter: DecimalFormatter, ) : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val source = text.text - val formatted = decimalFormatter.formatDigits(source) + var formatted = decimalFormatter.formatDigits(source).trim() + if (isCurrencyCodeDisplay) { + formatted += " $currencyCode" + } val offsetMapping = CustomOffsetMapping( source = source, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/GiniAmountTextInput.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/GiniAmountTextInput.kt index 81e847f1f0..229d6a9894 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/GiniAmountTextInput.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/amount/GiniAmountTextInput.kt @@ -30,6 +30,7 @@ import java.text.NumberFormat @Composable fun GiniAmountTextInput( amount: BigDecimal, + currencyCode: String, label: String, modifier: Modifier = Modifier, onValueChange: (BigDecimal) -> Unit, @@ -42,10 +43,8 @@ fun GiniAmountTextInput( val parsedAmount = decimalFormatter.parseAmount(amount) var text by remember { mutableStateOf(parsedAmount) } - - LaunchedEffect(key1 = parsedAmount) { // we need to reset text if amount was changed only - text = parsedAmount - } + + text = parsedAmount GiniTextInput( modifier = modifier, @@ -63,7 +62,11 @@ fun GiniAmountTextInput( }, trailingContent = trailingContent, colors = colors, - visualTransformation = DecimalInputVisualTransformation(decimalFormatter = decimalFormatter), + visualTransformation = DecimalInputVisualTransformation( + decimalFormatter = decimalFormatter, + currencyCode = currencyCode, + isCurrencyCodeDisplay = !enabled, + ), ) } @@ -91,6 +94,7 @@ private fun GiniTextInputPreview() { amount = BigDecimal("1234"), label = "Label Text", trailingContent = { }, + currencyCode = "EUR", onValueChange = {} ) } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/modifier/Width.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/modifier/Width.kt new file mode 100644 index 0000000000..d471681906 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/modifier/Width.kt @@ -0,0 +1,30 @@ +package net.gini.android.capture.ui.theme.modifier + +import android.content.res.Configuration +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.max +import net.gini.android.capture.R + +@Composable +fun Modifier.tabletMaxWidth(): Modifier { + return if (isTablet()) { + widthIn(max = dimensionResource(id = R.dimen.gc_tablet_width)) + } else { + fillMaxWidth() + } +} + +@Composable +private fun isTablet(): Boolean { + val configuration = LocalConfiguration.current + return if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + configuration.screenWidthDp > 840 + } else { + configuration.screenWidthDp > 600 + } +} \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/typography/GiniTypography.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/typography/GiniTypography.kt index 716c37180a..e15102324b 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/typography/GiniTypography.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/typography/GiniTypography.kt @@ -3,6 +3,7 @@ package net.gini.android.capture.ui.theme.typography import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import net.gini.android.capture.R @@ -23,6 +24,7 @@ data class GiniTypography( val overline: TextStyle = TextStyle.Default, ) +fun TextStyle.bold() = this.copy(fontWeight = FontWeight.Bold) @Composable fun extractGiniTypography() = GiniTypography( 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 5eb9e7e8a3..8f62f2e790 100644 --- a/capture-sdk/sdk/src/main/res/values-en/strings.xml +++ b/capture-sdk/sdk/src/main/res/values-en/strings.xml @@ -155,4 +155,8 @@ Useful tips \n\nTake a photo IBAN detected + + Select + Cancel + \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/res/values/strings.xml b/capture-sdk/sdk/src/main/res/values/strings.xml index 58ef480764..54a4d1fab6 100644 --- a/capture-sdk/sdk/src/main/res/values/strings.xml +++ b/capture-sdk/sdk/src/main/res/values/strings.xml @@ -173,4 +173,8 @@ Schließen, Schaltfläche \n\nMachen Sie ein Foto IBAN erkannt + + Auswählen + Abbrechen + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cec313620c..5dd13c6bc2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ hilt = "2.46.1" navigation_component = "2.7.6" compose-bom = "2024.06.00" accompanist-themeAdapter = "1.1.1" +compose-activity = "1.9.0" [libraries] android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" } @@ -115,6 +116,7 @@ tomlj = "org.tomlj:tomlj:1.1.1" json-testing = "org.json:json:20231013" compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" } compose-material3 = { module = "androidx.compose.material3:material3" } +compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" } compose-tools-uiTooling = { module = "androidx.compose.ui:ui-tooling" } compose-tools-uiToolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" } accompanist-themeAdapter = { module = "com.google.android.material:compose-theme-adapter-3", version.ref = "accompanist-themeAdapter" } \ No newline at end of file