Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bank-sdk): Skonto screen. Edge Cases #498

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,11 +22,17 @@ internal class SkontoFragmentViewModel(
data: SkontoData,
): SkontoFragmentContract.State.Ready {

val isSkontoSectionActive = true

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
val discount = data.skontoPercentageDiscounted

return SkontoFragmentContract.State.Ready(
isSkontoSectionActive = true,
Expand All @@ -34,6 +42,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
)
)
}

Expand All @@ -54,24 +69,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 {
Expand All @@ -96,10 +127,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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,18 +21,20 @@ data class SkontoScreenColors(
val withoutSkontoSectionColors: WithoutSkontoSectionColors,
val footerSectionColors: SkontoFooterSectionColors,
val datePickerColor: GiniDatePickerDialogColors,
val infoDialogColors: SkontoInfoDialogColors,
) {

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(),
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,
Expand All @@ -40,6 +43,7 @@ data class SkontoScreenColors(
withoutSkontoSectionColors = withoutSkontoSectionColors,
footerSectionColors = skontoFooterSectionColors,
datePickerColor = datePickerColor,
infoDialogColors = infoDialogColors,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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.badge.container,
textColor: Color = GiniTheme.colorScheme.badge.content,
) = DiscountLabelColorScheme(
backgroundColor = backgroundColor,
textColor = textColor,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
Expand All @@ -24,17 +26,21 @@ 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,
infoBannerColors: InfoBannerColors = InfoBannerColors.colors(),
cardBackgroundColor: Color = GiniTheme.colorScheme.card.container,
enabledHintTextColor: Color = GiniTheme.colorScheme.text.success,
successInfoBannerColors: InfoBannerColors = InfoBannerColors.success(),
warningInfoBannerColors: InfoBannerColors = InfoBannerColors.warning(),
errorInfoBannerColors: InfoBannerColors = InfoBannerColors.error(),
amountFieldColors: GiniTextInputColors = GiniTextInputColors.colors(),
dueDateTextFieldColor: GiniTextInputColors = GiniTextInputColors.colors(),
) = SkontoSectionColors(
titleTextColor = titleTextColor,
switchColors = switchColors,
cardBackgroundColor = cardBackgroundColor,
enabledHintTextColor = enabledHintTextColor,
infoBannerColors = infoBannerColors,
successInfoBannerColors = successInfoBannerColors,
warningInfoBannerColors = warningInfoBannerColors,
errorInfoBannerColors = errorInfoBannerColors,
amountFieldColors = amountFieldColors,
dueDateTextFieldColor = dueDateTextFieldColor
)
Expand All @@ -47,11 +53,34 @@ data class SkontoSectionColors(
val iconTint: Color,
) {
companion object {

@Composable
fun success(
backgroundColor: Color = GiniTheme.colorScheme.card.containerSuccess,
textColor: Color = GiniTheme.colorScheme.card.contentSuccess,
iconTint: Color = GiniTheme.colorScheme.card.contentSuccess,
) = InfoBannerColors(
backgroundColor = backgroundColor,
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 colors(
backgroundColor: Color = GiniTheme.colorScheme.chips.assistEnabled,
textColor: Color = GiniTheme.colorScheme.chips.suggestionEnabled,
iconTint: Color = GiniTheme.colorScheme.chips.suggestionEnabled,
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 10 additions & 1 deletion bank-sdk/sdk/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
<!-- region Section Discount -->
<string name="gbs_skonto_section_discount_title">With Skonto discount</string>
<string name="gbs_skonto_section_discount_hint_label_enabled">  •  Active</string>
<string name="gbs_skonto_section_discount_info_banner_message">Pay in %1$s days: %2$s off.</string>
<string name="gbs_skonto_section_discount_info_banner_normal_message">Pay in %1$s days: %2$s off.</string>
<string name="gbs_skonto_section_discount_info_banner_pay_today_message">Pay today: %1$s discount.</string>
<string name="gbs_skonto_section_discount_info_banner_pay_cash_message">A %1$s discount is available for cash payment within %2$s days</string>
<string name="gbs_skonto_section_discount_info_banner_date_expired_message">The %1$s discount has expired.</string>
<string name="gbs_skonto_section_discount_field_amount_hint">Final Amount</string>
<string name="gbs_skonto_section_discount_field_due_date_hint">Due date</string>
<!-- endregion -->
Expand All @@ -63,5 +66,11 @@
<string name="gbs_skonto_section_footer_label_discount">%1$s Skonto discount</string>
<string name="gbs_skonto_section_footer_continue_button_text">Continue to pay</string>
<!-- endregion -->
<!-- region InfoDialog -->
<string name="gbs_skonto_section_info_dialog_pay_today_message">A discount is indicated on this invoice, it will expire after today.</string>
<string name="gbs_skonto_section_info_dialog_pay_cash_message">A discount is indicated on this invoice, but it can only be used for cash deposit through a bank terminal.</string>
<string name="gbs_skonto_section_info_dialog_date_expired_message">You could have paid this invoice with a %1$s discount, but the discount period has already expired.</string>
<string name="gbs_skonto_section_info_dialog_ok_button_text">Got it</string>
<!-- endregion -->
<!-- endregion -->
</resources>
Loading
Loading