From 205b4154cfe5a94f2ac10f38af3b7931cda7a062 Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 18 Jul 2024 21:39:14 +0200 Subject: [PATCH 1/3] feat(bank-sdk): Skonto screen. Final color collection integration PP-619 --- .../skonto/colors/SkontoScreenColors.kt | 2 +- .../section/SkontoFooterSectionColors.kt | 6 +- .../section/SkontoInvoiceScanSectionColors.kt | 8 +- .../colors/section/SkontoSectionColors.kt | 10 +- .../section/WithoutSkontoSectionColors.kt | 4 +- .../button/filled/GiniButtonColors.kt | 4 +- .../picker/date/GiniDatePickerDialogColors.kt | 27 +- .../components/switcher/GiniSwitchColors.kt | 8 +- .../textinput/GiniTextInputColors.kt | 30 +- .../ui/components/topbar/GiniTopBarColors.kt | 6 +- .../ui/theme/colors/GiniColorPalette.kt | 515 ++++++++++++------ 11 files changed, 392 insertions(+), 228 deletions(-) diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt index 1c13774cf..f5acdb9f8 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt @@ -25,7 +25,7 @@ data class SkontoScreenColors( companion object { @Composable fun colors( - backgroundColor: Color = GiniTheme.colorScheme.background.background, + backgroundColor: Color = GiniTheme.colorScheme.background.primary, topAppBarColors: GiniTopBarColors = GiniTopBarColors.colors(), skontoInvoiceScanSectionColors: SkontoInvoiceScanSectionColors = SkontoInvoiceScanSectionColors.colors(), discountSectionColors: SkontoSectionColors = SkontoSectionColors.colors(), 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 e7efe0eba..316137746 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 @@ -19,7 +19,7 @@ data class SkontoFooterSectionColors( @Composable fun colors( - cardBackgroundColor: Color = GiniTheme.colorScheme.background.surface, + cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, titleTextColor: Color = GiniTheme.colorScheme.text.primary, amountTextColor: Color = GiniTheme.colorScheme.text.primary, discountLabelColorScheme: DiscountLabelColorScheme = DiscountLabelColorScheme.colors(), @@ -42,8 +42,8 @@ data class SkontoFooterSectionColors( @Composable fun colors( - backgroundColor: Color = GiniTheme.colorScheme.chips.suggestionEnabled, - textColor: Color = GiniTheme.colorScheme.chips.textSuggestionEnabled, + backgroundColor: Color = GiniTheme.colorScheme.card.containerSuccess, + textColor: Color = GiniTheme.colorScheme.card.contentSuccess, ) = DiscountLabelColorScheme( backgroundColor = backgroundColor, textColor = textColor, diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInvoiceScanSectionColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInvoiceScanSectionColors.kt index d22664ea1..5ac48d32f 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInvoiceScanSectionColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInvoiceScanSectionColors.kt @@ -18,12 +18,12 @@ data class SkontoInvoiceScanSectionColors( @Composable fun colors( - cardBackgroundColor: Color = GiniTheme.colorScheme.background.surface, + cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, titleTextColor: Color = GiniTheme.colorScheme.text.primary, subtitleTextColor: Color = GiniTheme.colorScheme.text.secondary, - iconBackgroundColor: Color = GiniTheme.colorScheme.icons.surfaceFilled, - iconTint: Color = GiniTheme.colorScheme.icons.trailing, - arrowTint: Color = GiniTheme.colorScheme.icons.trailing, + iconBackgroundColor: Color = GiniTheme.colorScheme.placeholder.background, + iconTint: Color = GiniTheme.colorScheme.placeholder.tint, + arrowTint: Color = GiniTheme.colorScheme.icons.secondary, ) = SkontoInvoiceScanSectionColors( cardBackgroundColor = cardBackgroundColor, titleTextColor = titleTextColor, diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt index 57ffe50c3..57c362c25 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt @@ -24,8 +24,8 @@ data class SkontoSectionColors( fun colors( titleTextColor: Color = GiniTheme.colorScheme.text.primary, switchColors: GiniSwitchColors = GiniSwitchColors.colors(), - cardBackgroundColor: Color = GiniTheme.colorScheme.background.surface, - enabledHintTextColor: Color = GiniTheme.colorScheme.text.status, + cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, + enabledHintTextColor: Color = GiniTheme.colorScheme.text.success, infoBannerColors: InfoBannerColors = InfoBannerColors.colors(), amountFieldColors: GiniTextInputColors = GiniTextInputColors.colors(), dueDateTextFieldColor: GiniTextInputColors = GiniTextInputColors.colors(), @@ -49,9 +49,9 @@ data class SkontoSectionColors( companion object { @Composable fun colors( - backgroundColor: Color = GiniTheme.colorScheme.chips.assistEnabled, - textColor: Color = GiniTheme.colorScheme.chips.suggestionEnabled, - iconTint: Color = GiniTheme.colorScheme.chips.suggestionEnabled, + backgroundColor: Color = GiniTheme.colorScheme.card.containerSuccess, + textColor: Color = GiniTheme.colorScheme.card.contentSuccess, + iconTint: Color = GiniTheme.colorScheme.card.contentSuccess, ) = InfoBannerColors( backgroundColor = backgroundColor, textColor = textColor, diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/WithoutSkontoSectionColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/WithoutSkontoSectionColors.kt index 3f41bd675..b8204970f 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/WithoutSkontoSectionColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/WithoutSkontoSectionColors.kt @@ -18,9 +18,9 @@ data class WithoutSkontoSectionColors( @Composable fun colors( titleTextColor: Color = GiniTheme.colorScheme.text.primary, - cardBackgroundColor: Color = GiniTheme.colorScheme.background.surface, + cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, amountFieldColors: GiniTextInputColors = GiniTextInputColors.colors(), - enabledHintTextColor: Color = GiniTheme.colorScheme.text.status, + enabledHintTextColor: Color = GiniTheme.colorScheme.text.success, ) = WithoutSkontoSectionColors( titleTextColor = titleTextColor, cardBackgroundColor = cardBackgroundColor, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/button/filled/GiniButtonColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/button/filled/GiniButtonColors.kt index 51e483fed..ebad464e6 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/button/filled/GiniButtonColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/button/filled/GiniButtonColors.kt @@ -14,8 +14,8 @@ data class GiniButtonColors( companion object { @Composable fun colors( - containerColor: Color = GiniTheme.colorScheme.button.surfacePrEnabled, - contentContent: Color = GiniTheme.colorScheme.button.textEnabled, + containerColor: Color = GiniTheme.colorScheme.button.container, + contentContent: Color = GiniTheme.colorScheme.button.content, ) = GiniButtonColors( containerColor = containerColor, contentContent = contentContent, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialogColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialogColors.kt index 94e7d9242..f28ee262f 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialogColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/picker/date/GiniDatePickerDialogColors.kt @@ -16,29 +16,23 @@ data class GiniDatePickerDialogColors( val textDateSelected: Color = Color.Unspecified, val textDateEnabled: Color = Color.Unspecified, val textDateDisabled: Color = Color.Unspecified, - val textMenu: Color = Color.Unspecified, val divider: Color = Color.Unspecified, - val iconMenu: Color = Color.Unspecified, - val iconButton: Color = Color.Unspecified, ) { companion object { @Composable fun colors( - dateSelected: Color = GiniTheme.colorScheme.datePicker.dateSelected, - borderDate: Color = GiniTheme.colorScheme.datePicker.borderDate, - textHeadline: Color = GiniTheme.colorScheme.datePicker.textHeadline, - textSupporting: Color = GiniTheme.colorScheme.datePicker.textSupporting, - textButtons: Color = GiniTheme.colorScheme.datePicker.textButtons, - textDateToday: Color = GiniTheme.colorScheme.datePicker.textDateToday, - textDateSelected: Color = GiniTheme.colorScheme.datePicker.textDateSelected, - textDateEnabled: Color = GiniTheme.colorScheme.datePicker.textDateEnabled, - textDateDisabled: Color = GiniTheme.colorScheme.datePicker.textDateDisabled, - textMenu: Color = GiniTheme.colorScheme.datePicker.textMenu, + dateSelected: Color = GiniTheme.colorScheme.datePicker.date.containerFocused, + borderDate: Color = GiniTheme.colorScheme.datePicker.date.containerOutlined, + textHeadline: Color = GiniTheme.colorScheme.datePicker.text.primary, + textSupporting: Color = GiniTheme.colorScheme.datePicker.text.secondary, + textButtons: Color = GiniTheme.colorScheme.datePicker.text.accent, + textDateToday: Color = GiniTheme.colorScheme.datePicker.text.primary, + textDateSelected: Color = GiniTheme.colorScheme.datePicker.date.contentFocused, + textDateEnabled: Color = GiniTheme.colorScheme.datePicker.text.primary, + textDateDisabled: Color = GiniTheme.colorScheme.datePicker.text.secondary, divider: Color = GiniTheme.colorScheme.datePicker.divider, - iconMenu: Color = GiniTheme.colorScheme.datePicker.iconMenu, - iconButton: Color = GiniTheme.colorScheme.datePicker.iconButton, ) = GiniDatePickerDialogColors( dateSelected = dateSelected, borderDate = borderDate, @@ -49,10 +43,7 @@ data class GiniDatePickerDialogColors( textDateSelected = textDateSelected, textDateEnabled = textDateEnabled, textDateDisabled = textDateDisabled, - textMenu = textMenu, divider = divider, - iconMenu = iconMenu, - iconButton = iconButton, ) } } \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/switcher/GiniSwitchColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/switcher/GiniSwitchColors.kt index fb65e9441..f4ad36bf8 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/switcher/GiniSwitchColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/switcher/GiniSwitchColors.kt @@ -16,10 +16,10 @@ data class GiniSwitchColors( @Composable fun colors( - checkedTrackColor: Color = GiniTheme.colorScheme.toggles.surfaceFocused, - checkedThumbColor: Color = Color.White, - uncheckedTrackColor: Color = GiniTheme.colorScheme.toggles.surfaceUnfocused, - uncheckedThumbColor: Color = GiniTheme.colorScheme.toggles.thumbUnfocused + checkedTrackColor: Color = GiniTheme.colorScheme.toggles.track.selected, + checkedThumbColor: Color = GiniTheme.colorScheme.toggles.thumb.selected, + uncheckedTrackColor: Color = GiniTheme.colorScheme.toggles.track.unselected, + uncheckedThumbColor: Color = GiniTheme.colorScheme.toggles.thumb.unselected ) = GiniSwitchColors( checkedTrackColor = checkedTrackColor, checkedThumbColor = checkedThumbColor, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt index f8125d492..e411b83b2 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt @@ -27,21 +27,21 @@ data class GiniTextInputColors( @Composable fun colors( - containerFocused: Color = GiniTheme.colorScheme.textField.containerFocused, - containerUnfocused: Color = GiniTheme.colorScheme.textField.containerUnfocused, - containerDisabled: Color = GiniTheme.colorScheme.textField.containerDisabled, - textFocused: Color = GiniTheme.colorScheme.textField.textFocused, - textUnfocused: Color = GiniTheme.colorScheme.textField.textUnfocused, - textDisabled: Color = GiniTheme.colorScheme.textField.textDisabled, - textError: Color = GiniTheme.colorScheme.textField.textError, - indicatorFocused: Color = GiniTheme.colorScheme.textField.indicatorFocused, - indicatorUnfocused: Color = GiniTheme.colorScheme.textField.indicatorUnfocused, - indicatorDisabled: Color = GiniTheme.colorScheme.textField.indicatorDisabled, - indicatorError: Color = GiniTheme.colorScheme.textField.indicatorError, - trailingContentFocused: Color = GiniTheme.colorScheme.textField.trailingContentFocused, - trailingContentUnfocused: Color = GiniTheme.colorScheme.textField.trailingContentUnfocused, - trailingContentDisabled: Color = GiniTheme.colorScheme.textField.trailingContentDisabled, - trailingContentError: Color = GiniTheme.colorScheme.textField.trailingContentError, + containerFocused: Color = GiniTheme.colorScheme.textField.container, + containerUnfocused: Color = GiniTheme.colorScheme.textField.container, + containerDisabled: Color = GiniTheme.colorScheme.textField.container, + textFocused: Color = GiniTheme.colorScheme.textField.text.focused, + textUnfocused: Color = GiniTheme.colorScheme.textField.text.unfocused, + textDisabled: Color = GiniTheme.colorScheme.textField.text.disabled, + textError: Color = GiniTheme.colorScheme.textField.text.error, + indicatorFocused: Color = GiniTheme.colorScheme.textField.indicator.focused, + indicatorUnfocused: Color = GiniTheme.colorScheme.textField.indicator.unfocused, + indicatorDisabled: Color = GiniTheme.colorScheme.textField.indicator.disabled, + indicatorError: Color = GiniTheme.colorScheme.textField.indicator.error, + trailingContentFocused: Color = GiniTheme.colorScheme.textField.content.trailing, + trailingContentUnfocused: Color = GiniTheme.colorScheme.textField.content.trailing, + trailingContentDisabled: Color = GiniTheme.colorScheme.textField.content.trailing, + trailingContentError: Color = GiniTheme.colorScheme.textField.content.trailing, ) = GiniTextInputColors( containerFocused = containerFocused, containerUnfocused = containerUnfocused, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/topbar/GiniTopBarColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/topbar/GiniTopBarColors.kt index b450d4d82..bac53b3fa 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/topbar/GiniTopBarColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/topbar/GiniTopBarColors.kt @@ -16,10 +16,10 @@ data class GiniTopBarColors( companion object { @Composable fun colors( - containerColor: Color = GiniTheme.colorScheme.background.bar, + containerColor: Color = GiniTheme.colorScheme.topAppBar.container, contentColor: Color = GiniTheme.colorScheme.text.primary, - navigationContentColor: Color = GiniTheme.colorScheme.text.primary, - actionContentColor: Color = GiniTheme.colorScheme.text.primary, + navigationContentColor: Color = GiniTheme.colorScheme.topAppBar.icon.navigation, + actionContentColor: Color = GiniTheme.colorScheme.topAppBar.icon.action, ) = GiniTopBarColors( containerColor = containerColor, contentColor = contentColor, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/colors/GiniColorPalette.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/colors/GiniColorPalette.kt index 0ed928f37..249d0a5f9 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/colors/GiniColorPalette.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/theme/colors/GiniColorPalette.kt @@ -2,6 +2,8 @@ package net.gini.android.capture.ui.theme.colors import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color +import net.gini.android.capture.ui.theme.colors.GiniColorScheme.Toggles.Thumb +import net.gini.android.capture.ui.theme.colors.GiniColorScheme.Toggles.Track /** * Color scheme of Gini. @@ -11,100 +13,188 @@ import androidx.compose.ui.graphics.Color @Immutable data class GiniColorScheme( val background: Background = Background(), - val button: Button = Button(), - val icons: Icons = Icons(), + val bottomBar: BottomBar = BottomBar(), + val topAppBar: TopAppBar = TopAppBar(), + val placeholder: Placeholder = Placeholder(), val text: Text = Text(), + val card: Card = Card(), + val badge: Badge = Badge(), + val button: Button = Button(), + val buttonOutlined: ButtonOutlined = ButtonOutlined(), val textField: TextField = TextField(), - val chips: Chips = Chips(), val toggles: Toggles = Toggles(), + val dialogs: Dialogs = Dialogs(), + val icons: Icons = Icons(), val datePicker: DatePicker = DatePicker(), ) { @Immutable data class Background( - val background: Color = Color.Unspecified, - val surface: Color = Color.Unspecified, - val bar: Color = Color.Unspecified, - val border: Color = Color.Unspecified, - val dialogs: Color = Color.Unspecified, + val primary: Color = Color.Unspecified, ) @Immutable - data class Button( - val surfacePrEnabled: Color = Color.Unspecified, - val textEnabled: Color = Color.Unspecified, + data class BottomBar( + val container: Color = Color.Unspecified, + val border: Color = Color.Unspecified, ) @Immutable - data class Icons( - val surfaceFilled: Color = Color.Unspecified, - val leading: Color = Color.Unspecified, - val trailingPrimary: Color = Color.Unspecified, - val trailing: Color = Color.Unspecified, - ) + data class TopAppBar( + val container: Color = Color.Unspecified, + val icon: Icon = Icon(), + ) { + @Immutable + data class Icon( + val action: Color = Color.Unspecified, + val navigation: Color = Color.Unspecified, + ) + } + @Immutable + data class Placeholder( + val background: Color = Color.Unspecified, + val tint: Color = Color.Unspecified, + ) @Immutable data class Text( - val system: Color = Color.Unspecified, val primary: Color = Color.Unspecified, val secondary: Color = Color.Unspecified, - val status: Color = Color.Unspecified, + val tertiary: Color = Color.Unspecified, + val accent: Color = Color.Unspecified, + val success: Color = Color.Unspecified, ) @Immutable - data class TextField( - val containerFocused: Color = Color.Unspecified, - val containerUnfocused: Color = Color.Unspecified, - val containerDisabled: Color = Color.Unspecified, - val textFocused: Color = Color.Unspecified, - val textUnfocused: Color = Color.Unspecified, - val textDisabled: Color = Color.Unspecified, - val textError: Color = Color.Unspecified, - val indicatorFocused: Color = Color.Unspecified, - val indicatorUnfocused: Color = Color.Unspecified, - val indicatorDisabled: Color = Color.Unspecified, - val indicatorError: Color = Color.Unspecified, - val trailingContentFocused: Color = Color.Unspecified, - val trailingContentUnfocused: Color = Color.Unspecified, - val trailingContentDisabled: Color = Color.Unspecified, - val trailingContentError: Color = Color.Unspecified, + data class Card( + val container: Color = Color.Unspecified, + val containerSuccess: Color = Color.Unspecified, + val contentSuccess: Color = Color.Unspecified, + val containerWarning: Color = Color.Unspecified, + val contentWarning: Color = Color.Unspecified, + val containerError: Color = Color.Unspecified, + val contentError: Color = Color.Unspecified, + ) + + @Immutable + data class Badge( + val container: Color = Color.Unspecified, + val content: Color = Color.Unspecified, ) @Immutable - data class Chips( - val suggestionEnabled: Color = Color.Unspecified, - val assistEnabled: Color = Color.Unspecified, - val textAssistEnabled: Color = Color.Unspecified, - val textSuggestionEnabled: Color = Color.Unspecified, + data class Button( + val container: Color = Color.Unspecified, + val containerLoading: Color = Color.Unspecified, + val content: Color = Color.Unspecified, + ) + + @Immutable + data class ButtonOutlined( + val container: Color = Color.Unspecified, + val content: Color = Color.Unspecified, ) + @Immutable + data class TextField( + val container: Color = Color.Unspecified, + val text: Text = Text(), + val label: Label = Label(), + val indicator: Indicator = Indicator(), + val cursor: Cursor = Cursor(), + val content: Content = Content() + ) { + @Immutable + data class Text( + val focused: Color = Color.Unspecified, + val unfocused: Color = Color.Unspecified, + val disabled: Color = Color.Unspecified, + val error: Color = Color.Unspecified, + ) + + @Immutable + data class Label( + val focused: Color = Color.Unspecified, + val unfocused: Color = Color.Unspecified, + val disabled: Color = Color.Unspecified, + val error: Color = Color.Unspecified, + ) + + @Immutable + data class Indicator( + val focused: Color = Color.Unspecified, + val unfocused: Color = Color.Unspecified, + val disabled: Color = Color.Unspecified, + val error: Color = Color.Unspecified, + ) + + @Immutable + data class Cursor( + val enabled: Color = Color.Unspecified, + val error: Color = Color.Unspecified, + ) + + @Immutable + data class Content( + val trailing: Color = Color.Unspecified, + ) + } @Immutable data class Toggles( - val surfaceFocused: Color = Color.Unspecified, - val surfaceUnfocused: Color = Color.Unspecified, - val surfaceDisabled: Color = Color.Unspecified, - val thumbFocused: Color = Color.Unspecified, - val thumbUnfocused: Color = Color.Unspecified, + val thumb: Thumb = Thumb(), + val track: Track = Track() + ) { + @Immutable + data class Thumb( + val selected: Color = Color.Unspecified, + val unselected: Color = Color.Unspecified, + ) + + @Immutable + data class Track( + val selected: Color = Color.Unspecified, + val unselected: Color = Color.Unspecified, + ) + } + + @Immutable + data class Dialogs( + val container: Color = Color.Unspecified, + val text: Color = Color.Unspecified, + val labelText: Color = Color.Unspecified + ) + + @Immutable + data class Icons( + val secondary: Color = Color.Unspecified, ) @Immutable data class DatePicker( - val dateSelected: Color = Color.Unspecified, - val borderDate: Color = Color.Unspecified, - val textHeadline: Color = Color.Unspecified, - val textSupporting: Color = Color.Unspecified, - val textButtons: Color = Color.Unspecified, - val textDateToday: Color = Color.Unspecified, - val textDateSelected: Color = Color.Unspecified, - val textDateEnabled: Color = Color.Unspecified, - val textDateDisabled: Color = Color.Unspecified, - val textMenu: Color = Color.Unspecified, + val container: Color = Color.Unspecified, val divider: Color = Color.Unspecified, - val iconMenu: Color = Color.Unspecified, - val iconButton: Color = Color.Unspecified, - ) + val icon: Color = Color.Unspecified, + + val text: Text = Text(), + val date: Date = Date(), + ) { + @Immutable + data class Text( + val primary: Color = Color.Unspecified, + val secondary: Color = Color.Unspecified, + val accent: Color = Color.Unspecified, + ) + + @Immutable + data class Date( + val containerFocused: Color = Color.Unspecified, + val containerOutlined: Color = Color.Unspecified, + val contentFocused: Color = Color.Unspecified, + val contentOutlined: Color = Color.Unspecified, + ) + } } /** @@ -114,67 +204,107 @@ internal fun giniLightColorScheme( giniColorPrimitives: GiniColorPrimitives = GiniColorPrimitives() ) = with(giniColorPrimitives) { GiniColorScheme( - background = GiniColorScheme.Background( + background = GiniColorScheme.Background(primary = light02), + bottomBar = GiniColorScheme.BottomBar( + container = light01, + border = light03 + ), + topAppBar = GiniColorScheme.TopAppBar( + container = light01, + icon = GiniColorScheme.TopAppBar.Icon( + action = dark01, + navigation = dark01 + ) + ), + placeholder = GiniColorScheme.Placeholder( background = light02, - surface = light01, - bar = light01, - border = light03, - dialogs = light01 - ), button = GiniColorScheme.Button( - surfacePrEnabled = accent01, - textEnabled = light01, - ), icons = GiniColorScheme.Icons( - surfaceFilled = light02, - leading = dark01, - trailingPrimary = dark01, - trailing = light06, - ), text = GiniColorScheme.Text( - system = accent01, + tint = dark06 + ), + text = GiniColorScheme.Text( primary = dark02, secondary = dark06, - status = success01, - ), textField = GiniColorScheme.TextField( - containerFocused = light01, - containerUnfocused = light01, - containerDisabled = Color.Transparent, - textFocused = dark02, - textUnfocused = dark02, - textDisabled = dark06, - textError = error02, - indicatorFocused = accent01, - indicatorUnfocused = light02, - indicatorDisabled = Color.Transparent, - indicatorError = error02, - trailingContentFocused = dark06, - trailingContentUnfocused = dark06, - trailingContentDisabled = dark06, - trailingContentError = error02, - ), chips = GiniColorScheme.Chips( - suggestionEnabled = success01, - assistEnabled = success04, - textAssistEnabled = success01, - textSuggestionEnabled = light01 - ), toggles = GiniColorScheme.Toggles( - surfaceFocused = accent03, - surfaceUnfocused = light03, - surfaceDisabled = Color.White, // TODO - thumbFocused = accent01, - thumbUnfocused = light01, - ), datePicker = GiniColorScheme.DatePicker( - dateSelected = accent01, - borderDate = accent01, - textHeadline = dark02, - textSupporting = dark06, - textButtons = accent01, - textDateToday = accent01, - textDateSelected = light01, - textDateEnabled = dark02, - textDateDisabled = light06, - textMenu = dark01, + tertiary = light06, + accent = accent01, + success = success01 + ), + card = GiniColorScheme.Card( + container = light01, + containerSuccess = success04, + contentSuccess = success01, + containerWarning = warning04, + contentWarning = warning05, + containerError = error04, + contentError = error01 + ), + badge = GiniColorScheme.Badge( + container = success01, + content = light01 + ), + button = GiniColorScheme.Button( + container = accent01, + containerLoading = accent01.copy(alpha = 0.24f), + content = light01 + ), + buttonOutlined = GiniColorScheme.ButtonOutlined( + container = light04, + content = dark02 + ), + textField = GiniColorScheme.TextField( + container = light01, + text = GiniColorScheme.TextField.Text( + focused = dark02, + unfocused = dark02, + disabled = dark06, + error = error02 + ), label = GiniColorScheme.TextField.Label( + focused = accent01, + unfocused = dark06, + disabled = dark06, + error = error02 + ), indicator = GiniColorScheme.TextField.Indicator( + focused = accent01, + unfocused = light03, + disabled = light01, + error = error02 + ), cursor = GiniColorScheme.TextField.Cursor( + enabled = accent01, + error = error02 + ), content = GiniColorScheme.TextField.Content( + trailing = dark06 + ) + ), + toggles = GiniColorScheme.Toggles( + thumb = Thumb( + selected = light01, + unselected = light01 + ), track = Track( + selected = accent01, + unselected = light04 + ) + ), + dialogs = GiniColorScheme.Dialogs( + container = light03, + text = dark01, + labelText = accent01 + ), + icons = GiniColorScheme.Icons(secondary = light06), + datePicker = GiniColorScheme.DatePicker( + container = light01, divider = light03, - iconMenu = dark01, - iconButton = dark01, + icon = dark01, + text = GiniColorScheme.DatePicker.Text( + primary = dark02, + secondary = dark06, + accent = accent01 + ), + date = GiniColorScheme.DatePicker.Date( + containerFocused = accent01, + containerOutlined = accent01, + contentFocused = light01, + contentOutlined = accent01 + ) ) + ) } @@ -185,66 +315,109 @@ internal fun giniDarkColorScheme( giniColorPrimitives: GiniColorPrimitives = GiniColorPrimitives() ) = with(giniColorPrimitives) { GiniColorScheme( - background = GiniColorScheme.Background( - background = dark01, - surface = dark02, - bar = dark02, - border = dark03, - dialogs = dark03 - ), button = GiniColorScheme.Button( - surfacePrEnabled = accent01, - textEnabled = light01 - ), icons = GiniColorScheme.Icons( - surfaceFilled = dark04, - leading = light01, - trailingPrimary = light01, - trailing = dark06 - ), text = GiniColorScheme.Text( - system = accent01, + background = GiniColorScheme.Background(primary = dark01), + bottomBar = GiniColorScheme.BottomBar( + container = dark02, + border = dark03 + ), + topAppBar = GiniColorScheme.TopAppBar( + container = dark02, + icon = GiniColorScheme.TopAppBar.Icon( + action = light01, + navigation = light01 + ) + ), + placeholder = GiniColorScheme.Placeholder( + background = dark04, + tint = light06 + ), + text = GiniColorScheme.Text( primary = light01, secondary = light06, - status = success01 - ), textField = GiniColorScheme.TextField( - containerFocused = dark02, - containerUnfocused = dark02, - containerDisabled = Color.Transparent, - textFocused = light01, - textUnfocused = light01, - textDisabled = dark06, - textError = error02, - indicatorFocused = accent01, - indicatorUnfocused = dark04, - indicatorDisabled = Color.Transparent, - indicatorError = error02, - trailingContentFocused = light06, - trailingContentUnfocused = light06, - trailingContentDisabled = light06, - trailingContentError = error02, - ), chips = GiniColorScheme.Chips( - suggestionEnabled = success01, - assistEnabled = success04, - textAssistEnabled = success01, - textSuggestionEnabled = light01 - ), toggles = GiniColorScheme.Toggles( - surfaceFocused = accent03, - surfaceUnfocused = dark03, - surfaceDisabled = Color.White, // TODO - thumbFocused = accent01, - thumbUnfocused = light01, - ), datePicker = GiniColorScheme.DatePicker( - dateSelected = accent01, - borderDate = accent01, - textHeadline = light01, - textSupporting = light06, - textButtons = accent01, - textDateToday = accent01, - textDateSelected = light01, - textDateEnabled = light01, - textDateDisabled = dark06, - textMenu = light01, + tertiary = dark06, + accent = accent01, + success = success01 + ), + card = GiniColorScheme.Card( + container = dark02, + containerSuccess = success04, + contentSuccess = success01, + containerWarning = warning04, + contentWarning = warning05, + containerError = error04, + contentError = error01 + ), + badge = GiniColorScheme.Badge( + container = success01, + content = light01 + ), + button = GiniColorScheme.Button( + container = accent01, + containerLoading = accent01.copy(alpha = 0.24f), + content = light01 + ), + buttonOutlined = GiniColorScheme.ButtonOutlined( + container = dark04, + content = light01 + ), + textField = GiniColorScheme.TextField( + container = dark02, + text = GiniColorScheme.TextField.Text( + focused = light01, + unfocused = light01, + disabled = dark06, + error = error02 + ), + label = GiniColorScheme.TextField.Label( + focused = accent01, + unfocused = light06, + disabled = light06, + error = error02 + ), + indicator = GiniColorScheme.TextField.Indicator( + focused = accent01, + unfocused = dark03, + disabled = dark02, + error = error02 + ), + cursor = GiniColorScheme.TextField.Cursor( + enabled = accent01, + error = error02 + ), + content = GiniColorScheme.TextField.Content( + trailing = light06 + ) + ), + toggles = GiniColorScheme.Toggles( + thumb = Thumb( + selected = light01, + unselected = light01 + ), track = Track( + selected = accent01, + unselected = dark04 + ) + ), + dialogs = GiniColorScheme.Dialogs( + container = dark03, + text = light01, + labelText = accent01 + ), + icons = GiniColorScheme.Icons(secondary = dark06), + datePicker = GiniColorScheme.DatePicker( + container = dark03, divider = dark04, - iconMenu = light01, - iconButton = light01, + icon = light01, + text = GiniColorScheme.DatePicker.Text( + primary = light01, + secondary = light06, + accent = accent01 + ), + date = GiniColorScheme.DatePicker.Date( + containerFocused = accent01, + containerOutlined = accent01, + contentFocused = light01, + contentOutlined = accent01 + ) ) ) } From cf3915e886b6673c325f4044ee696c6b38a56233 Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 18 Jul 2024 23:33:52 +0200 Subject: [PATCH 2/3] feat(bank-sdk): Skonto screen. Edge cases implementation PP-619 --- .../bank/sdk/capture/skonto/SkontoFragment.kt | 169 +++++++++++++++--- .../capture/skonto/SkontoFragmentContract.kt | 19 +- .../capture/skonto/SkontoFragmentViewModel.kt | 86 ++++++++- .../skonto/colors/SkontoScreenColors.kt | 6 +- .../section/SkontoFooterSectionColors.kt | 4 +- .../colors/section/SkontoInfoDialogColors.kt | 29 +++ .../colors/section/SkontoSectionColors.kt | 37 +++- .../sdk/src/main/res/values-en/strings.xml | 11 +- bank-sdk/sdk/src/main/res/values/strings.xml | 11 +- .../ui/components/textinput/GiniTextInput.kt | 8 + .../textinput/GiniTextInputColors.kt | 14 +- .../textinput/amount/GiniAmountTextInput.kt | 2 + 12 files changed, 361 insertions(+), 35 deletions(-) create mode 100644 bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInfoDialogColors.kt 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 a9fae9f03..b06faac74 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 @@ -9,6 +9,7 @@ import android.widget.FrameLayout import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,6 +26,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -43,6 +46,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged 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 @@ -55,6 +59,8 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -63,12 +69,12 @@ import net.gini.android.bank.sdk.GiniBank import net.gini.android.bank.sdk.R import net.gini.android.bank.sdk.capture.skonto.colors.SkontoScreenColors import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoFooterSectionColors +import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoInfoDialogColors import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoInvoiceScanSectionColors import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoSectionColors import net.gini.android.bank.sdk.capture.skonto.colors.section.WithoutSkontoSectionColors import net.gini.android.bank.sdk.capture.skonto.model.SkontoData import net.gini.android.bank.sdk.capture.util.currencyFormatterWithoutSymbol -import net.gini.android.capture.Amount import net.gini.android.capture.GiniCapture import net.gini.android.capture.ui.components.button.filled.GiniButton import net.gini.android.capture.ui.components.picker.date.GiniDatePickerDialog @@ -153,7 +159,9 @@ private fun ScreenContent( onBackClicked = {}, onHelpClicked = {}, customBottomNavBarAdapter = customBottomNavBarAdapter, - onProceedClicked = {} + onProceedClicked = {}, + onInfoBannerClicked = viewModel::onInfoBannerClicked, + onInfoDialogDismissed = viewModel::onInfoDialogDismissed ) } @@ -169,6 +177,8 @@ private fun ScreenStateContent( onProceedClicked: () -> Unit, isBottomNavigationBarEnabled: Boolean, customBottomNavBarAdapter: InjectedViewAdapterInstance?, + onInfoBannerClicked: () -> Unit, + onInfoDialogDismissed: () -> Unit, modifier: Modifier = Modifier, screenColorScheme: SkontoScreenColors = SkontoScreenColors.colors() ) { @@ -186,6 +196,8 @@ private fun ScreenStateContent( isBottomNavigationBarEnabled = isBottomNavigationBarEnabled, customBottomNavBarAdapter = customBottomNavBarAdapter, onProceedClicked = onProceedClicked, + onInfoBannerClicked = onInfoBannerClicked, + onInfoDialogDismissed = onInfoDialogDismissed, ) } @@ -205,6 +217,8 @@ private fun ScreenReadyState( customBottomNavBarAdapter: InjectedViewAdapterInstance?, modifier: Modifier = Modifier, screenColorScheme: SkontoScreenColors = SkontoScreenColors.colors(), + onInfoBannerClicked: () -> Unit, + onInfoDialogDismissed: () -> Unit, ) { val scrollState = rememberScrollState() @@ -244,6 +258,7 @@ private fun ScreenReadyState( modifier = Modifier.padding(vertical = 16.dp), colors = screenColorScheme.skontoSectionColors, amount = state.skontoAmount, + amountValidation = state.skontoAmountValidation, dueDate = state.discountDueDate, infoPaymentInDays = state.paymentInDays, infoDiscountValue = state.discountAmount, @@ -251,6 +266,8 @@ private fun ScreenReadyState( isActive = state.isSkontoSectionActive, onSkontoAmountChange = onDiscountAmountChange, onDueDateChanged = onDueDateChanged, + edgeCase = state.skontoEdgeCase, + onInfoBannerClicked = onInfoBannerClicked, ) WithoutSkontoSection( colors = screenColorScheme.withoutSkontoSectionColors, @@ -259,6 +276,31 @@ private fun ScreenReadyState( onFullAmountChange = onFullAmountChange, ) } + + if (state.edgeCaseInfoDialogVisible) { + val text = when (state.skontoEdgeCase) { + SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly -> + stringResource(id = R.string.gbs_skonto_section_info_dialog_pay_cash_message) + + SkontoFragmentContract.SkontoEdgeCase.SkontoExpired -> + stringResource( + id = R.string.gbs_skonto_section_info_dialog_date_expired_message, + state.discountAmount.toFloat().formatAsDiscountPercentage() + ) + + SkontoFragmentContract.SkontoEdgeCase.SkontoLastDay -> + stringResource( + id = R.string.gbs_skonto_section_info_dialog_pay_today_message, + ) + + null -> "" + } + InfoDialog( + text = text, + colors = screenColorScheme.infoDialogColors, + onDismissRequest = onInfoDialogDismissed + ) + } } } @@ -380,12 +422,15 @@ private fun YourInvoiceScanSection( private fun SkontoSection( colors: SkontoSectionColors, amount: SkontoData.Amount, + amountValidation: SkontoFragmentContract.State.Ready.SkontoAmountValidation, dueDate: LocalDate, infoPaymentInDays: Int, infoDiscountValue: BigDecimal, onActiveChange: (Boolean) -> Unit, onSkontoAmountChange: (BigDecimal) -> Unit, onDueDateChanged: (LocalDate) -> Unit, + onInfoBannerClicked: () -> Unit, + edgeCase: SkontoFragmentContract.SkontoEdgeCase?, modifier: Modifier = Modifier, isActive: Boolean, ) { @@ -427,11 +472,51 @@ private fun SkontoSection( onCheckedChange = onActiveChange, ) } + + val animatedDiscountAmount by animateFloatAsState( + targetValue = infoDiscountValue.toFloat(), + label = "discountAmount" + ) + + val infoBannerText = when (edgeCase) { + SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly -> + stringResource( + id = R.string.gbs_skonto_section_discount_info_banner_pay_cash_message, + animatedDiscountAmount.formatAsDiscountPercentage(), + infoPaymentInDays.toString() + ) + + SkontoFragmentContract.SkontoEdgeCase.SkontoExpired -> + stringResource( + id = R.string.gbs_skonto_section_discount_info_banner_date_expired_message, + animatedDiscountAmount.formatAsDiscountPercentage() + ) + + SkontoFragmentContract.SkontoEdgeCase.SkontoLastDay -> + stringResource( + id = R.string.gbs_skonto_section_discount_info_banner_pay_today_message, + animatedDiscountAmount.formatAsDiscountPercentage() + ) + + else -> stringResource( + id = R.string.gbs_skonto_section_discount_info_banner_normal_message, + infoPaymentInDays.toString(), + animatedDiscountAmount.formatAsDiscountPercentage() + ) + } + InfoBanner( - paymentIn = infoPaymentInDays.toString(), - discountValue = infoDiscountValue, + text = infoBannerText, modifier = Modifier.fillMaxWidth(), - colors = colors.infoBannerColors, + colors = when (edgeCase) { + SkontoFragmentContract.SkontoEdgeCase.SkontoLastDay, + SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly -> colors.warningInfoBannerColors + + SkontoFragmentContract.SkontoEdgeCase.SkontoExpired -> colors.errorInfoBannerColors + else -> colors.successInfoBannerColors + }, + onClicked = onInfoBannerClicked, + clickable = edgeCase != null, ) GiniAmountTextInput( amount = amount.amount, @@ -439,6 +524,7 @@ private fun SkontoSection( .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), @@ -499,38 +585,75 @@ private fun SkontoSection( @Composable private fun InfoBanner( colors: SkontoSectionColors.InfoBannerColors, - paymentIn: String, - discountValue: BigDecimal, + text: String, + clickable: Boolean, + onClicked: () -> Unit, modifier: Modifier = Modifier, + icon: Painter = painterResource(id = R.drawable.gbs_icon_important_info), ) { Row( - modifier = modifier.background( - color = colors.backgroundColor, RoundedCornerShape(8.dp) - ), + modifier = modifier + .background( + color = colors.backgroundColor, RoundedCornerShape(8.dp) + ) + .clickable(onClick = onClicked, enabled = clickable), verticalAlignment = Alignment.CenterVertically, ) { Icon( modifier = Modifier.padding(8.dp), - painter = painterResource(id = R.drawable.gbs_icon_important_info), + painter = icon, contentDescription = null, tint = colors.iconTint, ) - val animatedDiscountAmount by animateFloatAsState( - targetValue = discountValue.toFloat(), label = "discountAmount" - ) Text( - text = stringResource( - id = R.string.gbs_skonto_section_discount_info_banner_message, - paymentIn, - animatedDiscountAmount.formatAsDiscountPercentage() - ), + modifier = Modifier.padding(vertical = 8.dp), + text = text, style = GiniTheme.typography.subtitle2, color = colors.textColor, ) } } +@Composable +private fun InfoDialog( + text: String, + colors: SkontoInfoDialogColors, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, +) { + Dialog( + properties = DialogProperties(), + onDismissRequest = onDismissRequest + ) { + Card( + modifier = modifier.fillMaxWidth(), + ) { + Text( + modifier = Modifier.padding(top = 24.dp, start = 16.dp, end = 16.dp), + text = text, + style = GiniTheme.typography.caption1 + ) + Button( + modifier = Modifier + .padding(vertical = 16.dp) + .align(Alignment.End), + onClick = onDismissRequest, + shape = RoundedCornerShape(4.dp), + colors = ButtonDefaults.textButtonColors( + contentColor = colors.buttonTextColor, + ), + ) { + Text( + modifier = Modifier, + text = stringResource(id = R.string.gbs_skonto_section_info_dialog_ok_button_text), + style = GiniTheme.typography.button + ) + } + } + } +} + @Composable private fun WithoutSkontoSection( colors: WithoutSkontoSectionColors, @@ -738,7 +861,9 @@ private fun ScreenReadyStatePreview() { onBackClicked = {}, isBottomNavigationBarEnabled = false, onProceedClicked = {}, - customBottomNavBarAdapter = null + customBottomNavBarAdapter = null, + onInfoDialogDismissed = {}, + onInfoBannerClicked = {}, ) } } @@ -756,4 +881,8 @@ private val previewState = SkontoFragmentContract.State.Ready( discountDueDate = LocalDate.now(), fullAmount = SkontoData.Amount(BigDecimal("100"), "EUR"), totalAmount = SkontoData.Amount(BigDecimal("97"), "EUR"), + paymentMethod = SkontoData.SkontoPaymentMethod.PayPal, + skontoEdgeCase = SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly, + edgeCaseInfoDialogVisible = false, + skontoAmountValidation = SkontoFragmentContract.State.Ready.SkontoAmountValidation.Invalid.SkontoAmountGreaterOfFullAmount, ) \ 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 e6e4fbfad..0150215e2 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 @@ -12,10 +12,27 @@ internal object SkontoFragmentContract { val paymentInDays: Int, val discountAmount: BigDecimal, val skontoAmount: SkontoData.Amount, + val skontoAmountValidation: SkontoAmountValidation, val discountDueDate: LocalDate, val fullAmount: SkontoData.Amount, val totalAmount: SkontoData.Amount, - ) : State() + val paymentMethod: SkontoData.SkontoPaymentMethod, + val skontoEdgeCase: SkontoEdgeCase?, + val edgeCaseInfoDialogVisible: Boolean, + ) : State() { + sealed class SkontoAmountValidation { + object Valid : SkontoAmountValidation() + + sealed class Invalid : SkontoAmountValidation() { + object SkontoAmountGreaterOfFullAmount : Invalid() + } + } + } } + sealed class SkontoEdgeCase { + object SkontoLastDay : SkontoEdgeCase() + object PayByCashOnly : SkontoEdgeCase() + object SkontoExpired : SkontoEdgeCase() + } } \ 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 202060a5a..517be0c77 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 @@ -8,6 +8,8 @@ import net.gini.android.bank.sdk.capture.skonto.model.SkontoData import java.math.BigDecimal import java.math.RoundingMode import java.time.LocalDate +import java.time.temporal.ChronoUnit +import kotlin.math.absoluteValue internal class SkontoFragmentViewModel( private val data: SkontoData, @@ -26,6 +28,9 @@ internal class SkontoFragmentViewModel( if (isSkontoSectionActive) data.skontoAmountToPay else data.fullAmountToPay val discount = data.skontoPercentageDiscounted + val paymentMethod = data.skontoPaymentMethod ?: SkontoData.SkontoPaymentMethod.Unspecified + val edgeCase = extractSkontoEdgeCase(data.skontoDueDate, paymentMethod) + return SkontoFragmentContract.State.Ready( isSkontoSectionActive = true, paymentInDays = data.skontoRemainingDays, @@ -34,6 +39,13 @@ internal class SkontoFragmentViewModel( discountDueDate = data.skontoDueDate, fullAmount = data.fullAmountToPay, totalAmount = totalAmount, + paymentMethod = paymentMethod, + skontoEdgeCase = edgeCase, + edgeCaseInfoDialogVisible = edgeCase != null, + skontoAmountValidation = validateSkontoAmount( + skontoAmount = data.skontoAmountToPay, + fullAmount = data.fullAmountToPay + ) ) } @@ -54,24 +66,40 @@ internal class SkontoFragmentViewModel( fun onSkontoAmountFieldChanged(newValue: BigDecimal) = viewModelScope.launch { val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return@launch - val discount = calculateDiscount(newValue, currentState.fullAmount.amount) + val discount = calculateDiscount(newValue, currentState.fullAmount.amount).coerceAtLeast( + BigDecimal.ZERO + ) val totalAmount = if (currentState.isSkontoSectionActive) newValue else currentState.fullAmount.amount + val newSkontoAmount = currentState.skontoAmount.copy(amount = newValue) + val newTotalAmount = currentState.totalAmount.copy(amount = totalAmount) + stateFlow.emit( currentState.copy( - skontoAmount = currentState.skontoAmount.copy(amount = newValue), + skontoAmount = newSkontoAmount, discountAmount = discount, - totalAmount = currentState.totalAmount.copy(amount = totalAmount), + totalAmount = newTotalAmount, + skontoAmountValidation = validateSkontoAmount(newSkontoAmount, currentState.fullAmount) ) ) } fun onSkontoDueDateChanged(newDate: LocalDate) = viewModelScope.launch { val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return@launch - stateFlow.emit(currentState.copy(discountDueDate = newDate)) + val newPayInDays = ChronoUnit.DAYS.between(newDate, LocalDate.now()).absoluteValue.toInt() + stateFlow.emit( + currentState.copy( + discountDueDate = newDate, + paymentInDays = newPayInDays, + skontoEdgeCase = extractSkontoEdgeCase( + dueDate = newDate, + paymentMethod = currentState.paymentMethod + ) + ) + ) } fun onFullAmountFieldChanged(newValue: BigDecimal) = viewModelScope.launch { @@ -96,10 +124,60 @@ internal class SkontoFragmentViewModel( ) } + fun onInfoBannerClicked() = viewModelScope.launch { + val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return@launch + stateFlow.emit( + currentState.copy( + edgeCaseInfoDialogVisible = true, + ) + ) + } + + fun onInfoDialogDismissed() = viewModelScope.launch { + val currentState = stateFlow.value as? SkontoFragmentContract.State.Ready ?: return@launch + stateFlow.emit( + currentState.copy( + edgeCaseInfoDialogVisible = false, + ) + ) + } + private fun calculateDiscount(skontoAmount: BigDecimal, fullAmount: BigDecimal): BigDecimal { if (fullAmount == BigDecimal.ZERO) return BigDecimal("100") return BigDecimal.ONE .minus(skontoAmount.divide(fullAmount, 4, RoundingMode.HALF_UP)) .multiply(BigDecimal("100")) } + + 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 extractSkontoEdgeCase( + dueDate: LocalDate, + paymentMethod: SkontoData.SkontoPaymentMethod, + ): SkontoFragmentContract.SkontoEdgeCase? { + val today = LocalDate.now() + return when { + dueDate.isBefore(today) -> + SkontoFragmentContract.SkontoEdgeCase.SkontoExpired + + dueDate == today -> + SkontoFragmentContract.SkontoEdgeCase.SkontoLastDay + + paymentMethod == SkontoData.SkontoPaymentMethod.Cash -> + SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly + + else -> null + } + } } \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt index f5acdb9f8..b0e44b5de 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/SkontoScreenColors.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoFooterSectionColors +import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoInfoDialogColors import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoInvoiceScanSectionColors import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoSectionColors import net.gini.android.bank.sdk.capture.skonto.colors.section.WithoutSkontoSectionColors @@ -20,6 +21,7 @@ data class SkontoScreenColors( val withoutSkontoSectionColors: WithoutSkontoSectionColors, val footerSectionColors: SkontoFooterSectionColors, val datePickerColor: GiniDatePickerDialogColors, + val infoDialogColors: SkontoInfoDialogColors, ) { companion object { @@ -31,7 +33,8 @@ data class SkontoScreenColors( discountSectionColors: SkontoSectionColors = SkontoSectionColors.colors(), withoutSkontoSectionColors: WithoutSkontoSectionColors = WithoutSkontoSectionColors.colors(), skontoFooterSectionColors: SkontoFooterSectionColors = SkontoFooterSectionColors.colors(), - datePickerColor: GiniDatePickerDialogColors = GiniDatePickerDialogColors.colors() + datePickerColor: GiniDatePickerDialogColors = GiniDatePickerDialogColors.colors(), + infoDialogColors: SkontoInfoDialogColors = SkontoInfoDialogColors.colors(), ) = SkontoScreenColors( backgroundColor = backgroundColor, topAppBarColors = topAppBarColors, @@ -40,6 +43,7 @@ data class SkontoScreenColors( withoutSkontoSectionColors = withoutSkontoSectionColors, footerSectionColors = skontoFooterSectionColors, datePickerColor = datePickerColor, + infoDialogColors = infoDialogColors, ) } } 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 316137746..f565d4cf7 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 @@ -42,8 +42,8 @@ data class SkontoFooterSectionColors( @Composable fun colors( - backgroundColor: Color = GiniTheme.colorScheme.card.containerSuccess, - textColor: Color = GiniTheme.colorScheme.card.contentSuccess, + backgroundColor: Color = GiniTheme.colorScheme.badge.container, + textColor: Color = GiniTheme.colorScheme.badge.content, ) = DiscountLabelColorScheme( backgroundColor = backgroundColor, textColor = textColor, diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInfoDialogColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInfoDialogColors.kt new file mode 100644 index 000000000..4b5a0cf6b --- /dev/null +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoInfoDialogColors.kt @@ -0,0 +1,29 @@ +package net.gini.android.bank.sdk.capture.skonto.colors.section + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color +import net.gini.android.capture.ui.theme.GiniTheme + +@Immutable +data class SkontoInfoDialogColors( + val cardBackgroundColor: Color, + val textColor: Color, + val buttonTextColor: Color, +) { + + companion object { + + @Composable + fun colors( + cardBackgroundColor: Color = GiniTheme.colorScheme.dialogs.container, + textColor: Color = GiniTheme.colorScheme.dialogs.text, + buttonTextColor: Color = GiniTheme.colorScheme.dialogs.labelText, + ) = SkontoInfoDialogColors( + cardBackgroundColor = cardBackgroundColor, + textColor = textColor, + buttonTextColor = buttonTextColor, + ) + } + +} \ No newline at end of file diff --git a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt index 57c362c25..9462fae50 100644 --- a/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt +++ b/bank-sdk/sdk/src/main/java/net/gini/android/bank/sdk/capture/skonto/colors/section/SkontoSectionColors.kt @@ -13,7 +13,9 @@ data class SkontoSectionColors( val switchColors: GiniSwitchColors, val cardBackgroundColor: Color, val enabledHintTextColor: Color, - val infoBannerColors: InfoBannerColors, + val successInfoBannerColors: InfoBannerColors, + val warningInfoBannerColors: InfoBannerColors, + val errorInfoBannerColors: InfoBannerColors, val amountFieldColors: GiniTextInputColors, val dueDateTextFieldColor: GiniTextInputColors, ) { @@ -26,7 +28,9 @@ data class SkontoSectionColors( switchColors: GiniSwitchColors = GiniSwitchColors.colors(), cardBackgroundColor: Color = GiniTheme.colorScheme.card.container, enabledHintTextColor: Color = GiniTheme.colorScheme.text.success, - infoBannerColors: InfoBannerColors = InfoBannerColors.colors(), + successInfoBannerColors: InfoBannerColors = InfoBannerColors.success(), + warningInfoBannerColors: InfoBannerColors = InfoBannerColors.warning(), + errorInfoBannerColors: InfoBannerColors = InfoBannerColors.error(), amountFieldColors: GiniTextInputColors = GiniTextInputColors.colors(), dueDateTextFieldColor: GiniTextInputColors = GiniTextInputColors.colors(), ) = SkontoSectionColors( @@ -34,7 +38,9 @@ data class SkontoSectionColors( switchColors = switchColors, cardBackgroundColor = cardBackgroundColor, enabledHintTextColor = enabledHintTextColor, - infoBannerColors = infoBannerColors, + successInfoBannerColors = successInfoBannerColors, + warningInfoBannerColors = warningInfoBannerColors, + errorInfoBannerColors = errorInfoBannerColors, amountFieldColors = amountFieldColors, dueDateTextFieldColor = dueDateTextFieldColor ) @@ -47,8 +53,9 @@ data class SkontoSectionColors( val iconTint: Color, ) { companion object { + @Composable - fun colors( + fun success( backgroundColor: Color = GiniTheme.colorScheme.card.containerSuccess, textColor: Color = GiniTheme.colorScheme.card.contentSuccess, iconTint: Color = GiniTheme.colorScheme.card.contentSuccess, @@ -57,6 +64,28 @@ data class SkontoSectionColors( textColor = textColor, iconTint = iconTint, ) + + @Composable + fun warning( + backgroundColor: Color = GiniTheme.colorScheme.card.containerWarning, + textColor: Color = GiniTheme.colorScheme.card.contentWarning, + iconTint: Color = GiniTheme.colorScheme.card.contentWarning, + ) = InfoBannerColors( + backgroundColor = backgroundColor, + textColor = textColor, + iconTint = iconTint, + ) + + @Composable + fun error( + backgroundColor: Color = GiniTheme.colorScheme.card.containerError, + textColor: Color = GiniTheme.colorScheme.card.contentError, + iconTint: Color = GiniTheme.colorScheme.card.contentError, + ) = InfoBannerColors( + backgroundColor = backgroundColor, + textColor = textColor, + iconTint = iconTint, + ) } } } \ 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 b23592e14..d55018d75 100644 --- a/bank-sdk/sdk/src/main/res/values-en/strings.xml +++ b/bank-sdk/sdk/src/main/res/values-en/strings.xml @@ -49,7 +49,10 @@ With Skonto discount   •  Active - Pay in %1$s days: %2$s off. + Pay in %1$s days: %2$s off. + Pay today: %1$s discount. + A %1$s discount is available for cash payment within %2$s days + The %1$s discount has expired. Final Amount Due date @@ -63,5 +66,11 @@ %1$s Skonto discount Continue to pay + + A discount is indicated on this invoice, it will expire after today. + A discount is indicated on this invoice, but it can only be used for cash deposit through a bank terminal. + You could have paid this invoice with a %1$s discount, but the discount period has already expired. + Got it + diff --git a/bank-sdk/sdk/src/main/res/values/strings.xml b/bank-sdk/sdk/src/main/res/values/strings.xml index f9a959272..d619b5979 100644 --- a/bank-sdk/sdk/src/main/res/values/strings.xml +++ b/bank-sdk/sdk/src/main/res/values/strings.xml @@ -49,7 +49,10 @@ Mit Skonto   •  Aktiviert - Zahlung in %1$s Tagen: %2$s Skonto. + Zahlung in %1$s Tagen: %2$s Skonto. + Zahlung heute: %1$s Skonto + %1$s Skonto sind bei Barzahlung innerhalb von %2$s Tagen verfügbar. + Die %1$s Skonto sind abgelaufen. Betrag nach Abzug Fälligkeitsdatum @@ -63,5 +66,11 @@ %1$s Skonto Zahlung fortsetzen + + In dieser Rechnung ist ein Skonto enthalten – die Frist läuft heute ab. + In dieser Rechnung ist ein Skonto angegeben, das jedoch nur bei Bareinzahlung über einen Bankterminal gültig ist. + Diese Rechnung hätten Sie mit %1$s Skonto bezahlen können, die Skontofrist ist jedoch bereits abgelaufen. + Verstanden + diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt index 998b70ed1..1122d492f 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt @@ -21,6 +21,7 @@ fun GiniTextInput( onValueChange: (String) -> Unit, trailingContent: @Composable () -> Unit = {}, enabled: Boolean = true, + isError: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, colors: GiniTextInputColors = GiniTextInputColors.colors(), visualTransformation: VisualTransformation = VisualTransformation.None, @@ -29,6 +30,7 @@ fun GiniTextInput( modifier = modifier, text = text, enabled = enabled, + isError = isError, keyboardOptions = keyboardOptions, label = { Text( @@ -51,6 +53,7 @@ fun GiniTextInput( modifier: Modifier = Modifier, trailingContent: @Composable () -> Unit = {}, enabled: Boolean = true, + isError: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, colors: GiniTextInputColors = GiniTextInputColors.colors(), visualTransformation: VisualTransformation = VisualTransformation.None, @@ -62,6 +65,7 @@ fun GiniTextInput( keyboardOptions = keyboardOptions, textStyle = GiniTheme.typography.subtitle1, label = label, + isError = isError, visualTransformation = visualTransformation, colors = with(colors) { TextFieldDefaults.colors( @@ -72,6 +76,10 @@ fun GiniTextInput( unfocusedTextColor = textUnfocused, disabledTextColor = textDisabled, errorTextColor = textError, + unfocusedLabelColor = labelUnfocused, + errorLabelColor = labelError, + focusedLabelColor = labelFocused, + disabledLabelColor = labelDisabled, focusedIndicatorColor = indicatorFocused, unfocusedIndicatorColor = indicatorUnfocused, disabledIndicatorColor = indicatorDisabled, diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt index e411b83b2..40e84341e 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInputColors.kt @@ -10,6 +10,10 @@ data class GiniTextInputColors( val containerFocused: Color, val containerUnfocused: Color, val containerDisabled: Color, + val labelError: Color, + val labelUnfocused: Color, + val labelFocused: Color, + val labelDisabled: Color, val textFocused: Color, val textUnfocused: Color, val textDisabled: Color, @@ -33,7 +37,7 @@ data class GiniTextInputColors( textFocused: Color = GiniTheme.colorScheme.textField.text.focused, textUnfocused: Color = GiniTheme.colorScheme.textField.text.unfocused, textDisabled: Color = GiniTheme.colorScheme.textField.text.disabled, - textError: Color = GiniTheme.colorScheme.textField.text.error, + textError: Color = GiniTheme.colorScheme.textField.text.focused, indicatorFocused: Color = GiniTheme.colorScheme.textField.indicator.focused, indicatorUnfocused: Color = GiniTheme.colorScheme.textField.indicator.unfocused, indicatorDisabled: Color = GiniTheme.colorScheme.textField.indicator.disabled, @@ -42,6 +46,10 @@ data class GiniTextInputColors( trailingContentUnfocused: Color = GiniTheme.colorScheme.textField.content.trailing, trailingContentDisabled: Color = GiniTheme.colorScheme.textField.content.trailing, trailingContentError: Color = GiniTheme.colorScheme.textField.content.trailing, + labelError: Color = GiniTheme.colorScheme.textField.label.error, + labelUnfocused: Color = GiniTheme.colorScheme.textField.label.unfocused, + labelFocused: Color = GiniTheme.colorScheme.textField.label.focused, + labelDisabled: Color = GiniTheme.colorScheme.textField.label.disabled, ) = GiniTextInputColors( containerFocused = containerFocused, containerUnfocused = containerUnfocused, @@ -58,6 +66,10 @@ data class GiniTextInputColors( trailingContentUnfocused = trailingContentUnfocused, trailingContentDisabled = trailingContentDisabled, trailingContentError = trailingContentError, + labelError = labelError, + labelUnfocused = labelUnfocused, + labelFocused = labelFocused, + labelDisabled = labelDisabled, ) } } \ No newline at end of file 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 1861c675f..81e847f1f 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 @@ -35,6 +35,7 @@ fun GiniAmountTextInput( onValueChange: (BigDecimal) -> Unit, trailingContent: @Composable () -> Unit = {}, enabled: Boolean = true, + isError: Boolean = false, decimalFormatter: DecimalFormatter = DecimalFormatter(), colors: GiniTextInputColors = GiniTextInputColors.colors(), ) { @@ -50,6 +51,7 @@ fun GiniAmountTextInput( modifier = modifier, text = text, enabled = enabled, + isError = isError, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, imeAction = ImeAction.Done, From ed9ee745eb706d82885ec17bd42379fb24b8cdba Mon Sep 17 00:00:00 2001 From: Niko Date: Sat, 20 Jul 2024 00:49:46 +0200 Subject: [PATCH 3/3] feat(bank-sdk): Skonto screen. Edge cases implementation PP-619 --- .../bank/sdk/capture/skonto/SkontoFragment.kt | 76 ++++++++++++++----- .../capture/skonto/SkontoFragmentViewModel.kt | 9 ++- .../picker/date/GiniDatePickerDialog.kt | 4 +- .../ui/components/textinput/GiniTextInput.kt | 11 +++ 4 files changed, 77 insertions(+), 23 deletions(-) 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 b06faac74..d8e7946be 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 @@ -1,6 +1,10 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + 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 @@ -10,6 +14,9 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -21,7 +28,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -30,11 +36,14 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -44,7 +53,7 @@ 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.onFocusChanged +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 @@ -54,8 +63,6 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -87,7 +94,13 @@ import net.gini.android.capture.ui.theme.GiniTheme 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() { @@ -437,7 +450,7 @@ private fun SkontoSection( val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy") var isDatePickerVisible by remember { mutableStateOf(false) } - val amountFieldFocusRequester = remember { FocusRequester() } + val dueDateFieldFocusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current Card( @@ -538,28 +551,29 @@ private fun SkontoSection( }, ) + val dueDateOnClickSource = remember { MutableInteractionSource() } + val pressed by dueDateOnClickSource.collectIsPressedAsState() + + LaunchedEffect(key1 = pressed) { + if (pressed) { + isDatePickerVisible = true + } + } + GiniTextInput( modifier = Modifier .fillMaxWidth() .padding(top = 16.dp) - .onFocusChanged { - if (it.isFocused) { - isDatePickerVisible = true - focusManager.clearFocus() - } - } - .focusRequester(amountFieldFocusRequester), + .focusable(false), enabled = isActive, + interactionSource = dueDateOnClickSource, + readOnly = true, colors = colors.dueDateTextFieldColor, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Number, - imeAction = ImeAction.Done, - ), onValueChange = { /* Ignored */ }, text = dueDate.format(dateFormatter), label = stringResource(id = R.string.gbs_skonto_section_discount_field_due_date_hint), trailingContent = { - AnimatedVisibility(visible = isActive) { + androidx.compose.animation.AnimatedVisibility(visible = isActive) { Icon( painter = painterResource(id = R.drawable.gbs_icon_calendar), contentDescription = null, @@ -577,11 +591,35 @@ private fun SkontoSection( isDatePickerVisible = false onDueDateChanged(it) }, - date = dueDate + date = dueDate, + selectableDates = getSkontoSelectableDates() ) } } +private fun getSkontoSelectableDates() = object : SelectableDates { + + val minDateCalendar = Calendar.getInstance().apply { + set(Calendar.MILLISECONDS_IN_DAY, 0) + } + + val maxDateCalendar = Calendar.getInstance().apply { + add(Calendar.MONTH, 6) + } + + val minTime = minDateCalendar.timeInMillis + val maxTime = maxDateCalendar.timeInMillis + + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return (minTime..maxTime).contains(utcTimeMillis) + } + + override fun isSelectableYear(year: Int): Boolean { + return (minDateCalendar.get(Calendar.YEAR)..maxDateCalendar.get(Calendar.YEAR)) + .contains(year) + } +} + @Composable private fun InfoBanner( colors: SkontoSectionColors.InfoBannerColors, @@ -636,7 +674,7 @@ private fun InfoDialog( ) Button( modifier = Modifier - .padding(vertical = 16.dp) + .padding(16.dp) .align(Alignment.End), onClick = onDismissRequest, shape = RoundedCornerShape(4.dp), 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 517be0c77..5ecab38e0 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 @@ -22,15 +22,18 @@ internal class SkontoFragmentViewModel( data: SkontoData, ): SkontoFragmentContract.State.Ready { - val isSkontoSectionActive = true - val totalAmount = - if (isSkontoSectionActive) data.skontoAmountToPay else data.fullAmountToPay val discount = data.skontoPercentageDiscounted val paymentMethod = data.skontoPaymentMethod ?: SkontoData.SkontoPaymentMethod.Unspecified val edgeCase = extractSkontoEdgeCase(data.skontoDueDate, paymentMethod) + val isSkontoSectionActive = edgeCase != SkontoFragmentContract.SkontoEdgeCase.PayByCashOnly + && edgeCase != SkontoFragmentContract.SkontoEdgeCase.SkontoExpired + + val totalAmount = + if (isSkontoSectionActive) data.skontoAmountToPay else data.fullAmountToPay + return SkontoFragmentContract.State.Ready( isSkontoSectionActive = true, paymentInDays = data.skontoRemainingDays, 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 26a13172b..01e19ea03 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 @@ -11,6 +11,7 @@ import androidx.compose.material3.Card import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDatePickerState @@ -32,11 +33,12 @@ fun GiniDatePickerDialog( onSaved: (LocalDate) -> Unit, modifier: Modifier = Modifier, date: LocalDate = LocalDate.now(), + selectableDates: SelectableDates = DatePickerDefaults.AllDates, colors: GiniDatePickerDialogColors = GiniDatePickerDialogColors.colors() ) { val dateState = rememberDatePickerState( - selectableDates = DatePickerDefaults.AllDates, + selectableDates = selectableDates, initialSelectedDateMillis = date.atStartOfDay(ZoneId.systemDefault()).toInstant() .toEpochMilli(), ) diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt index 1122d492f..22b7eb812 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/ui/components/textinput/GiniTextInput.kt @@ -1,12 +1,15 @@ package net.gini.android.capture.ui.components.textinput import android.content.res.Configuration +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview @@ -22,15 +25,19 @@ fun GiniTextInput( trailingContent: @Composable () -> Unit = {}, enabled: Boolean = true, isError: Boolean = false, + readOnly: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, colors: GiniTextInputColors = GiniTextInputColors.colors(), visualTransformation: VisualTransformation = VisualTransformation.None, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { GiniTextInput( modifier = modifier, text = text, enabled = enabled, + readOnly = readOnly, isError = isError, + interactionSource = interactionSource, keyboardOptions = keyboardOptions, label = { Text( @@ -54,9 +61,11 @@ fun GiniTextInput( trailingContent: @Composable () -> Unit = {}, enabled: Boolean = true, isError: Boolean = false, + readOnly: Boolean = false, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, colors: GiniTextInputColors = GiniTextInputColors.colors(), visualTransformation: VisualTransformation = VisualTransformation.None, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { TextField( modifier = modifier, @@ -66,6 +75,8 @@ fun GiniTextInput( textStyle = GiniTheme.typography.subtitle1, label = label, isError = isError, + readOnly = readOnly, + interactionSource = interactionSource, visualTransformation = visualTransformation, colors = with(colors) { TextFieldDefaults.colors(