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

feature(bank-sdk): Skonto Invoice Preview #536

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import net.gini.android.bank.sdk.capture.digitalinvoice.DigitalInvoiceException
import net.gini.android.bank.sdk.capture.digitalinvoice.DigitalInvoiceFragment
import net.gini.android.bank.sdk.capture.digitalinvoice.DigitalInvoiceFragmentListener
import net.gini.android.bank.sdk.capture.digitalinvoice.LineItemsValidator
import net.gini.android.bank.sdk.capture.extractions.skonto.SkontoInvoiceHighlightsExtractor
import net.gini.android.bank.sdk.capture.skonto.SkontoDataExtractor
import net.gini.android.bank.sdk.capture.skonto.SkontoFragment
import net.gini.android.bank.sdk.capture.skonto.SkontoFragmentListener
import net.gini.android.bank.sdk.di.getGiniBankKoin
import net.gini.android.bank.sdk.util.disallowScreenshots

import net.gini.android.capture.CaptureSDKResult
Expand Down Expand Up @@ -47,6 +49,9 @@ class CaptureFlowFragment(private val openWithDocument: Document? = null) :
private lateinit var navController: NavController
private lateinit var captureFlowFragmentListener: CaptureFlowFragmentListener

private val skontoInvoiceHighlightsExtractor: SkontoInvoiceHighlightsExtractor
by getGiniBankKoin().inject()

// Remember the original primary navigation fragment so that we can restore it when this fragment is detached
private var originalPrimaryNavigationFragment: Fragment? = null

Expand Down Expand Up @@ -191,8 +196,15 @@ class CaptureFlowFragment(private val openWithDocument: Document? = null) :
result.compoundExtractions
)

val highlightBoxes = skontoInvoiceHighlightsExtractor.extract(
result.compoundExtractions
)

navController.navigate(
GiniCaptureFragmentDirections.toSkontoFragment(data = skontoData)
GiniCaptureFragmentDirections.toSkontoFragment(
data = skontoData,
invoiceHighlights = highlightBoxes.toTypedArray(),
)
)
} catch (e: Exception) {
finishWithResult(result)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.gini.android.bank.sdk.capture

import net.gini.android.bank.sdk.capture.extractions.skonto.SkontoInvoiceHighlightsExtractor
import org.koin.dsl.module

val captureFlowFragmentModule = module {
single { SkontoInvoiceHighlightsExtractor() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.gini.android.bank.sdk.capture.extractions.skonto

import net.gini.android.bank.sdk.capture.skonto.extractDataByKeys
import net.gini.android.bank.sdk.capture.skonto.model.SkontoInvoiceHighlightBoxes
import net.gini.android.capture.network.model.GiniCaptureCompoundExtraction
import net.gini.android.capture.network.model.GiniCaptureSpecificExtraction
import kotlin.jvm.Throws

internal class SkontoInvoiceHighlightsExtractor {

fun extract(
compoundExtractions: Map<String, GiniCaptureCompoundExtraction>,
): List<SkontoInvoiceHighlightBoxes> {

val skontoDiscountMaps = compoundExtractions["skontoDiscounts"]?.specificExtractionMaps
?: throw NoSuchElementException("Field `compoundExtractions.skontoDiscounts` is missing")

return skontoDiscountMaps.map { skontoDiscountData ->
val skontoPercentageDiscounted = extractPercentageDiscountedOrError(skontoDiscountData)
val skontoPaymentMethod = skontoDiscountData.extractDataByKeys("skontoPaymentMethod")
val skontoAmountToPay = extractAmountToPayOrError(skontoDiscountData)
val skontoRemainingDays = extractRemainingDaysOrError(skontoDiscountData)

val skontoDueDate = extractDueDateOrError(skontoDiscountData)

val skontoAmountDiscounted = skontoDiscountData.extractDataByKeys(
"skontoAmountDiscounted",
"skontoAmountDiscountedCalculated"
)

SkontoInvoiceHighlightBoxes(
skontoPercentageDiscounted = skontoPercentageDiscounted.box,
skontoPaymentMethod = skontoPaymentMethod?.box,
skontoRemainingDays = skontoRemainingDays.box,
skontoDueDate = skontoDueDate.box,
skontoAmountToPay = skontoAmountToPay.box,
skontoAmountDiscounted = skontoAmountDiscounted?.box,
)
}
}

@Throws(NoSuchElementException::class)
private fun extractPercentageDiscountedOrError(
skontoDiscountMap: Map<String, GiniCaptureSpecificExtraction>
): GiniCaptureSpecificExtraction =
skontoDiscountMap.extractDataByKeys(
"skontoPercentageDiscounted",
"skontoPercentageDiscountedCalculated",
) ?: throw NoSuchElementException("Data for `PercentageDiscounted` is missing")

@Throws(NoSuchElementException::class)
private fun extractAmountToPayOrError(
skontoDiscountMap: Map<String, GiniCaptureSpecificExtraction>
): GiniCaptureSpecificExtraction =
skontoDiscountMap.extractDataByKeys(
"skontoAmountToPay",
"skontoAmountToPayCalculated"
) ?: throw NoSuchElementException("Skonto data for `AmountToPay` is missing")

@Throws(NoSuchElementException::class)
private fun extractRemainingDaysOrError(
skontoDiscountMap: Map<String, GiniCaptureSpecificExtraction>
): GiniCaptureSpecificExtraction =
skontoDiscountMap.extractDataByKeys(
"skontoRemainingDays",
"skontoRemainingDaysCalculated"
) ?: throw NoSuchElementException("Skonto data for `RemainingDays` is missing")

@Throws(NoSuchElementException::class)
private fun extractDueDateOrError(
skontoDiscountMap: Map<String, GiniCaptureSpecificExtraction>
): GiniCaptureSpecificExtraction =
skontoDiscountMap.extractDataByKeys(
"skontoDueDate",
"skontoDueDateCalculated"
) ?: throw NoSuchElementException("Skonto data for `DueDate` is missing")

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package net.gini.android.bank.sdk.capture.skonto

import android.annotation.SuppressLint
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.icu.util.Calendar
import android.os.Bundle
Expand Down Expand Up @@ -57,6 +58,7 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
Expand All @@ -67,6 +69,8 @@ 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.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import net.gini.android.bank.sdk.GiniBank
Expand All @@ -79,7 +83,7 @@ import net.gini.android.bank.sdk.capture.skonto.colors.section.SkontoSectionColo
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.bank.sdk.di.getGiniKoin
import net.gini.android.bank.sdk.di.getGiniBankKoin
import net.gini.android.bank.sdk.util.disallowScreenshots
import net.gini.android.capture.GiniCapture
import net.gini.android.capture.internal.util.ActivityHelper.forcePortraitOrientationOnPhones
Expand All @@ -105,7 +109,7 @@ class SkontoFragment : Fragment() {

private val args: SkontoFragmentArgs by navArgs<SkontoFragmentArgs>()

private val viewModel: SkontoFragmentViewModel by getGiniKoin().inject {
private val viewModel: SkontoFragmentViewModel by getGiniBankKoin().inject {
parametersOf(args.data)
}

Expand Down Expand Up @@ -155,6 +159,14 @@ class SkontoFragment : Fragment() {
navigateBack = {
findNavController()
.navigate(SkontoFragmentDirections.toCaptureFragment())
},
navigateToInvoiceScreen = {
findNavController()
.navigate(
SkontoFragmentDirections.toSkontoInvoiceFragment(
args.invoiceHighlights
)
)
}
)
}
Expand All @@ -163,6 +175,23 @@ class SkontoFragment : Fragment() {
}
}

@Composable
@SuppressLint("ComposableNaming")
private fun SkontoFragmentViewModel.collectSideEffect(
action: (SkontoFragmentContract.SideEffect) -> Unit
) {

val lifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(sideEffectFlow, lifecycleOwner) {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
sideEffectFlow.collect {
action(it)
}
}
}
}

@Composable
private fun ScreenContent(
navigateBack: () -> Unit,
Expand All @@ -171,11 +200,19 @@ private fun ScreenContent(
screenColorScheme: SkontoScreenColors = SkontoScreenColors.colors(),
isBottomNavigationBarEnabled: Boolean,
customBottomNavBarAdapter: InjectedViewAdapterInstance<SkontoNavigationBarBottomAdapter>?,
navigateToInvoiceScreen: () -> Unit,
) {

BackHandler { navigateBack() }

val state by viewModel.stateFlow.collectAsState()

viewModel.collectSideEffect {
when (it) {
SkontoFragmentContract.SideEffect.OpenInvoiceScreen -> navigateToInvoiceScreen()
}
}

ScreenStateContent(
modifier = modifier,
state = state,
Expand All @@ -187,9 +224,10 @@ private fun ScreenContent(
isBottomNavigationBarEnabled = isBottomNavigationBarEnabled,
onBackClicked = navigateBack,
customBottomNavBarAdapter = customBottomNavBarAdapter,
onProceedClicked = { viewModel.onProceedClicked() },
onProceedClicked = viewModel::onProceedClicked,
onInfoBannerClicked = viewModel::onInfoBannerClicked,
onInfoDialogDismissed = viewModel::onInfoDialogDismissed
onInfoDialogDismissed = viewModel::onInfoDialogDismissed,
onInvoiceClicked = viewModel::onInvoiceClicked
)
}

Expand All @@ -206,6 +244,7 @@ private fun ScreenStateContent(
customBottomNavBarAdapter: InjectedViewAdapterInstance<SkontoNavigationBarBottomAdapter>?,
onInfoBannerClicked: () -> Unit,
onInfoDialogDismissed: () -> Unit,
onInvoiceClicked: () -> Unit,
modifier: Modifier = Modifier,
screenColorScheme: SkontoScreenColors = SkontoScreenColors.colors()
) {
Expand All @@ -224,6 +263,7 @@ private fun ScreenStateContent(
onProceedClicked = onProceedClicked,
onInfoBannerClicked = onInfoBannerClicked,
onInfoDialogDismissed = onInfoDialogDismissed,
onInvoiceClicked = onInvoiceClicked,
)
}

Expand All @@ -233,6 +273,7 @@ private fun ScreenStateContent(
private fun ScreenReadyState(
onBackClicked: () -> Unit,
onProceedClicked: () -> Unit,
onInvoiceClicked: () -> Unit,
state: SkontoFragmentContract.State.Ready,
onDiscountSectionActiveChange: (Boolean) -> Unit,
onDiscountAmountChange: (BigDecimal) -> Unit,
Expand Down Expand Up @@ -276,12 +317,21 @@ private fun ScreenReadyState(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
YourInvoiceScanSection(
modifier = Modifier
.padding(top = 8.dp)
.tabletMaxWidth(),
colorScheme = screenColorScheme.invoiceScanSectionColors,
onClick = onInvoiceClicked,
)
SkontoSection(
modifier = Modifier
.padding(vertical = 16.dp)
.tabletMaxWidth(),
colors = screenColorScheme.skontoSectionColors,
amount = state.skontoAmount,
Expand Down Expand Up @@ -370,9 +420,12 @@ private fun NavigationActionBack(
private fun YourInvoiceScanSection(
ndubkov-distcotech marked this conversation as resolved.
Show resolved Hide resolved
modifier: Modifier = Modifier,
colorScheme: SkontoInvoiceScanSectionColors,
onClick: () -> Unit,
) {
Card(
modifier = modifier.fillMaxWidth(),
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick),
shape = RectangleShape,
colors = CardDefaults.cardColors(containerColor = colorScheme.cardBackgroundColor)
) {
Expand Down Expand Up @@ -932,6 +985,7 @@ private fun ScreenReadyStatePreview() {
customBottomNavBarAdapter = null,
onInfoDialogDismissed = {},
onInfoBannerClicked = {},
onInvoiceClicked = {}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ internal object SkontoFragmentContract {
) : State()
}

sealed interface SideEffect {
object OpenInvoiceScreen : SideEffect
}

sealed class SkontoEdgeCase {
object SkontoLastDay : SkontoEdgeCase()
object PayByCashOnly : SkontoEdgeCase()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.gini.android.bank.sdk.capture.skonto

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import net.gini.android.bank.sdk.capture.skonto.model.SkontoData
Expand All @@ -18,6 +19,8 @@ internal class SkontoFragmentViewModel(
val stateFlow: MutableStateFlow<SkontoFragmentContract.State> =
MutableStateFlow(createInitalState(data))

val sideEffectFlow: MutableSharedFlow<SkontoFragmentContract.SideEffect> = MutableSharedFlow()

private var listener: SkontoFragmentListener? = null

fun setListener(listener: SkontoFragmentListener?) {
Expand Down Expand Up @@ -175,6 +178,10 @@ internal class SkontoFragmentViewModel(
)
}

fun onInvoiceClicked() = viewModelScope.launch {
sideEffectFlow.emit(SkontoFragmentContract.SideEffect.OpenInvoiceScreen)
}

private fun calculateDiscount(skontoAmount: BigDecimal, fullAmount: BigDecimal): BigDecimal {
if (fullAmount == BigDecimal.ZERO) return BigDecimal("100")
return BigDecimal.ONE
Expand Down Expand Up @@ -205,4 +212,4 @@ internal class SkontoFragmentViewModel(
else -> null
}
}
}
}
Loading
Loading