diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POLabeledContent.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POLabeledContent.kt index 798320cf9..ed52ada61 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POLabeledContent.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POLabeledContent.kt @@ -2,10 +2,12 @@ package com.processout.sdk.ui.core.component import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.style.POLabeledContentStyle import com.processout.sdk.ui.core.theme.ProcessOutTheme.colors import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography @@ -50,3 +52,35 @@ fun POLabeledContent( } } } + +/** @suppress */ +@ProcessOutInternalApi +object POLabeledContent { + + @Immutable + data class Style( + val label: POText.Style, + val text: POText.Style, + val copyButton: POButton.Style + ) + + val default: Style + @Composable get() = Style( + label = POText.Style( + color = colors.text.placeholder, + textStyle = typography.s12(FontWeight.Medium) + ), + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + copyButton = POCopyButton.default + ) + + @Composable + fun custom(style: POLabeledContentStyle) = Style( + label = POText.custom(style = style.label), + text = POText.custom(style = style.text), + copyButton = default.copyButton + ) +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POTextWithIcon.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POTextWithIcon.kt index aa9f59a42..92b5b5cc6 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POTextWithIcon.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POTextWithIcon.kt @@ -93,7 +93,7 @@ object POTextWithIcon { ) return Style( text = text, - iconResId = R.drawable.po_icon_info, + iconResId = R.drawable.po_icon_warning_diamond, iconColorFilter = ColorFilter.tint(color = text.color) ) } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioField.kt index ecac0ddfe..572173274 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioField.kt @@ -256,7 +256,7 @@ object PORadioField { else if (isError) style.error else style.normal - internal fun Style.radioButtonStyle() = PORadioButton.Style( + fun Style.radioButtonStyle() = PORadioButton.Style( normalColor = normal.radioButtonColor, selectedColor = selected.radioButtonColor, errorColor = error.radioButtonColor, diff --git a/ui-core/src/main/res/drawable/po_icon_info.xml b/ui-core/src/main/res/drawable/po_icon_info.xml deleted file mode 100644 index 5cfb1acb5..000000000 --- a/ui-core/src/main/res/drawable/po_icon_info.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt index 6b34ce12c..83f0b0abe 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutEvent.kt @@ -1,11 +1,11 @@ package com.processout.sdk.ui.checkout -import androidx.compose.ui.text.input.TextFieldValue import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse import com.processout.sdk.api.model.response.POGooglePayCardTokenizationData import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.card.scanner.recognition.POScannedCard import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration +import com.processout.sdk.ui.shared.state.FieldValue import org.json.JSONObject internal sealed interface DynamicCheckoutEvent { @@ -16,7 +16,7 @@ internal sealed interface DynamicCheckoutEvent { data class FieldValueChanged( val paymentMethodId: String, val fieldId: String, - val value: TextFieldValue + val value: FieldValue ) : DynamicCheckoutEvent data class FieldFocusChanged( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt index fd7c6b5f2..24e8a37f9 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt @@ -11,8 +11,10 @@ import coil.request.ImageRequest import coil.request.ImageResult import com.processout.sdk.R import com.processout.sdk.api.dispatcher.POEventDispatcher -import com.processout.sdk.api.model.event.PONativeAlternativePaymentMethodEvent.WillSubmitParameters -import com.processout.sdk.api.model.request.* +import com.processout.sdk.api.model.request.POCardTokenizationProcessingRequest +import com.processout.sdk.api.model.request.POCardTokenizationShouldContinueRequest +import com.processout.sdk.api.model.request.POInvoiceAuthorizationRequest +import com.processout.sdk.api.model.request.POInvoiceRequest import com.processout.sdk.api.model.request.POInvoiceRequest.ExpandedProperty.Companion.paymentMethods import com.processout.sdk.api.model.request.POInvoiceRequest.ExpandedProperty.Companion.transaction import com.processout.sdk.api.model.response.* @@ -59,7 +61,9 @@ import com.processout.sdk.ui.checkout.delegate.PODynamicCheckoutEvent.* import com.processout.sdk.ui.napm.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow -import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesRequest +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent.WillSubmitParameters import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration import com.processout.sdk.ui.shared.extension.orElse import com.processout.sdk.ui.shared.state.FieldValue @@ -148,8 +152,8 @@ internal class DynamicCheckoutInteractor( } private fun cancelWebAuthorization() { - with(_state.value) { - if (selectedPaymentMethod != null || pendingSubmitPaymentMethod != null) { + _state.value.let { + if (it.selectedPaymentMethod != null || it.pendingSubmitPaymentMethod != null) { interactorScope.launch { _sideEffects.send(DynamicCheckoutSideEffect.CancelWebAuthorization) } @@ -432,8 +436,8 @@ internal class DynamicCheckoutInteractor( _state.value.paymentMethods.find { it.id == id } private fun activePaymentMethod(): PaymentMethod? = - with(_state.value) { - processingPaymentMethod ?: selectedPaymentMethod + _state.value.let { + it.processingPaymentMethod ?: it.selectedPaymentMethod } private fun onPaymentMethodSelected(event: PaymentMethodSelected) { @@ -569,20 +573,24 @@ internal class DynamicCheckoutInteractor( private fun onFieldValueChanged(event: FieldValueChanged) { when (val paymentMethod = paymentMethod(event.paymentMethodId)) { - is Card -> cardTokenization.onEvent( - CardTokenizationEvent.FieldValueChanged(event.fieldId, event.value) - ) + is Card -> if (event.value is FieldValue.Text) { + cardTokenization.onEvent( + CardTokenizationEvent.FieldValueChanged(event.fieldId, event.value.value) + ) + } is NativeAlternativePayment -> nativeAlternativePayment.onEvent( - NativeAlternativePaymentEvent.FieldValueChanged(event.fieldId, FieldValue.Text(event.value)) + NativeAlternativePaymentEvent.FieldValueChanged(event.fieldId, event.value) ) - else -> _state.update { state -> - state.copy( - paymentMethods = state.paymentMethods.map { - if (it.id == paymentMethod?.id) { - updatedPaymentMethod(it, event.fieldId, event.value) - } else it - } - ) + else -> if (event.value is FieldValue.Text) { + _state.update { state -> + state.copy( + paymentMethods = state.paymentMethods.map { + if (it.id == paymentMethod?.id) { + updatedPaymentMethod(it, event.fieldId, event.value.value) + } else it + } + ) + } } } } @@ -593,9 +601,11 @@ internal class DynamicCheckoutInteractor( value: TextFieldValue ): PaymentMethod = when (paymentMethod) { is AlternativePayment -> when (fieldId) { - FieldId.SAVE_PAYMENT_METHOD -> with(paymentMethod) { + FieldId.SAVE_PAYMENT_METHOD -> { POLogger.debug("Field is edited by the user: %s = %s", fieldId, value.text) - copy(savePaymentMethodField = savePaymentMethodField?.copy(value = value)) + paymentMethod.copy( + savePaymentMethodField = paymentMethod.savePaymentMethodField?.copy(value = value) + ) } else -> paymentMethod } @@ -840,16 +850,21 @@ internal class DynamicCheckoutInteractor( if (paymentMethod.id != paymentMethodId) { return } - result.onSuccess { response -> - authorizeInvoice( - paymentMethod = paymentMethod, - source = response.gatewayToken, - allowFallbackToSale = true - ) - }.onFailure { failure -> - invalidateInvoice( - reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) + when (paymentMethod) { + is NativeAlternativePayment -> nativeAlternativePayment.onEvent( + NativeAlternativePaymentEvent.RedirectResult(result) ) + else -> result.onSuccess { response -> + authorizeInvoice( + paymentMethod = paymentMethod, + source = response.gatewayToken, + allowFallbackToSale = true + ) + }.onFailure { failure -> + invalidateInvoice( + reason = PODynamicCheckoutInvoiceInvalidationReason.Failure(failure) + ) + } } } } @@ -1081,7 +1096,7 @@ internal class DynamicCheckoutInteractor( eventDispatcher.send(request.toResponse(shouldContinue)) } } - eventDispatcher.subscribeForRequest( + eventDispatcher.subscribeForRequest( coroutineScope = interactorScope ) { request -> activePaymentMethod()?.let { paymentMethod -> @@ -1128,9 +1143,17 @@ internal class DynamicCheckoutInteractor( _sideEffects.send(permissionRequest) POLogger.info("System permission requested: %s", permissionRequest) } - is NativeAlternativePaymentSideEffect.Redirect -> { - // TODO - } + is NativeAlternativePaymentSideEffect.Redirect -> + activePaymentMethod()?.let { paymentMethod -> + _state.update { it.copy(processingPaymentMethod = paymentMethod) } + _sideEffects.send( + DynamicCheckoutSideEffect.AlternativePayment( + paymentMethodId = paymentMethod.id, + redirectUrl = sideEffect.redirectUrl, + returnUrl = sideEffect.returnUrl + ) + ) + } } } } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/DynamicCheckoutScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutScreen.kt similarity index 83% rename from ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/DynamicCheckoutScreen.kt rename to ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutScreen.kt index 4e1211c22..3b1f6353c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/DynamicCheckoutScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutScreen.kt @@ -1,8 +1,7 @@ @file:Suppress("MayBeConstant", "MemberVisibilityCanBePrivate") -package com.processout.sdk.ui.checkout.screen +package com.processout.sdk.ui.checkout -import android.view.Gravity import androidx.annotation.DrawableRes import androidx.compose.animation.* import androidx.compose.animation.core.LinearEasing @@ -21,6 +20,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -35,24 +35,22 @@ import com.processout.sdk.api.model.response.POImageResource import com.processout.sdk.ui.card.tokenization.CardTokenizationEvent import com.processout.sdk.ui.card.tokenization.screen.CardTokenizationContent import com.processout.sdk.ui.card.tokenization.screen.CardTokenizationScreen -import com.processout.sdk.ui.checkout.DynamicCheckoutEvent import com.processout.sdk.ui.checkout.DynamicCheckoutEvent.* -import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.LongAnimationDurationMillis +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.PaymentLogoSize +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.PaymentSuccessStyle +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.RowComponentSpacing +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.SectionHeaderStyle +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.ShortAnimationDurationMillis +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.SuccessImageHeight +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.SuccessImageWidth +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.animatedBackgroundColor +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.cardTokenizationStyle +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.nativeAlternativePaymentStyle +import com.processout.sdk.ui.checkout.DynamicCheckoutScreen.toButtonStyle import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.* import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.Field.CheckboxField import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.RegularPayment.Content.* -import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.LongAnimationDurationMillis -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.PaymentLogoSize -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.PaymentSuccessStyle -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.RowComponentSpacing -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.SectionHeaderStyle -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.ShortAnimationDurationMillis -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.SuccessImageHeight -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.SuccessImageWidth -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.animatedBackgroundColor -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.cardTokenizationStyle -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.toButtonStyle import com.processout.sdk.ui.core.R import com.processout.sdk.ui.core.component.* import com.processout.sdk.ui.core.component.POButton.HighlightedStyle @@ -63,8 +61,8 @@ import com.processout.sdk.ui.core.component.field.code.POCodeField import com.processout.sdk.ui.core.component.field.dropdown.PODropdownField import com.processout.sdk.ui.core.component.field.radio.PORadioButton import com.processout.sdk.ui.core.component.field.radio.PORadioField -import com.processout.sdk.ui.core.component.field.radio.PORadioGroup -import com.processout.sdk.ui.core.component.field.radio.PORadioGroup.toRadioButtonStyle +import com.processout.sdk.ui.core.component.field.radio.PORadioField.radioButtonStyle +import com.processout.sdk.ui.core.component.stepper.POStepper import com.processout.sdk.ui.core.state.POActionState import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.style.POBrandButtonStateStyle @@ -76,10 +74,16 @@ import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions import com.processout.sdk.ui.core.theme.ProcessOutTheme.shapes import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography +import com.processout.sdk.ui.napm.NativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState +import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Stage +import com.processout.sdk.ui.napm.screen.NativeAlternativePaymentContent +import com.processout.sdk.ui.napm.screen.NativeAlternativePaymentScreen import com.processout.sdk.ui.shared.component.AndroidTextView import com.processout.sdk.ui.shared.component.GooglePayButton import com.processout.sdk.ui.shared.extension.* import com.processout.sdk.ui.shared.state.FieldState +import com.processout.sdk.ui.shared.state.FieldValue @Composable internal fun DynamicCheckoutScreen( @@ -166,7 +170,7 @@ private fun Content( ) { POMessageBox( text = state.errorMessage, - style = style.messageBox, + style = style.errorMessageBox, modifier = Modifier.padding(bottom = spacing.large), horizontalArrangement = Arrangement.spacedBy(RowComponentSpacing), enterAnimationDelayMillis = ShortAnimationDurationMillis @@ -383,7 +387,8 @@ private fun RegularPayments( RegularPaymentContent( payment = payment, onEvent = onEvent, - style = style + style = style, + isLightTheme = isLightTheme ) if (index != payments.elements.lastIndex) { HorizontalDivider( @@ -438,7 +443,7 @@ private fun RegularPayment( PORadioButton( selected = payment.state.selected, onClick = { onEvent(PaymentMethodSelected(id = payment.id)) }, - style = style.radioGroup.toRadioButtonStyle() + style = style.radioField.radioButtonStyle() ) } } @@ -447,7 +452,8 @@ private fun RegularPayment( private fun RegularPaymentContent( payment: RegularPayment, onEvent: (DynamicCheckoutEvent) -> Unit, - style: DynamicCheckoutScreen.Style + style: DynamicCheckoutScreen.Style, + isLightTheme: Boolean ) { AnimatedVisibility( visible = payment.state.selected && !payment.state.loading, @@ -476,16 +482,35 @@ private fun RegularPaymentContent( when (payment.content) { is Card -> CardTokenizationContent( state = payment.content.state, - onEvent = { onEvent(it.map(paymentMethodId = payment.id)) }, + onEvent = { + it.map(paymentMethodId = payment.id)?.let { event -> + onEvent(event) + } + }, style = style.cardTokenizationStyle(), withActionsContainer = false ) - is NativeAlternativePayment -> NativeAlternativePayment( - id = payment.id, - state = payment.content.state, - onEvent = onEvent, - style = style - ) + is NativeAlternativePayment -> when (val state = payment.content.state) { + is NativeAlternativePaymentViewModelState.Loaded -> { + when (state.content.stage) { + is Stage.Pending, + is Stage.Completed -> LocalFocusManager.current.clearFocus(force = true) + else -> {} + } + NativeAlternativePaymentContent( + content = state.content, + onEvent = { + it.map(paymentMethodId = payment.id)?.let { event -> + onEvent(event) + } + }, + style = style.nativeAlternativePaymentStyle(), + isPrimaryActionEnabled = state.primaryAction?.let { it.enabled && !it.loading } ?: false, + isLightTheme = isLightTheme + ) + } + else -> {} + } is AlternativePayment -> AlternativePayment( id = payment.id, state = payment.content, @@ -558,7 +583,7 @@ private fun CheckboxField( FieldValueChanged( paymentMethodId = id, fieldId = state.id, - value = TextFieldValue(text = it.toString()) + value = FieldValue.Text(value = TextFieldValue(text = it.toString())) ) ) }, @@ -667,11 +692,11 @@ private fun Success( private fun CardTokenizationEvent.map( paymentMethodId: String -): DynamicCheckoutEvent = when (this) { +): DynamicCheckoutEvent? = when (this) { is CardTokenizationEvent.FieldValueChanged -> FieldValueChanged( paymentMethodId = paymentMethodId, fieldId = id, - value = value + value = FieldValue.Text(value = value) ) is CardTokenizationEvent.FieldFocusChanged -> FieldFocusChanged( paymentMethodId = paymentMethodId, @@ -682,8 +707,36 @@ private fun CardTokenizationEvent.map( actionId = id, paymentMethodId = paymentMethodId ) - is CardTokenizationEvent.CardScannerResult -> CardScannerResult(card = card) is CardTokenizationEvent.Dismiss -> Dismiss(failure = failure) + is CardTokenizationEvent.CardScannerResult -> null // Ignore, handled by dynamic checkout events. +} + +private fun NativeAlternativePaymentEvent.map( + paymentMethodId: String +): DynamicCheckoutEvent? = when (this) { + is NativeAlternativePaymentEvent.FieldValueChanged -> FieldValueChanged( + paymentMethodId = paymentMethodId, + fieldId = id, + value = value + ) + is NativeAlternativePaymentEvent.FieldFocusChanged -> FieldFocusChanged( + paymentMethodId = paymentMethodId, + fieldId = id, + isFocused = isFocused + ) + is NativeAlternativePaymentEvent.Action -> Action( + actionId = id, + paymentMethodId = paymentMethodId + ) + is NativeAlternativePaymentEvent.DialogAction -> DialogAction( + actionId = id, + paymentMethodId = paymentMethodId, + isConfirmed = isConfirmed + ) + is NativeAlternativePaymentEvent.ActionConfirmationRequested -> ActionConfirmationRequested(id = id) + is NativeAlternativePaymentEvent.Dismiss -> Dismiss(failure = failure) + is NativeAlternativePaymentEvent.PermissionRequestResult, + is NativeAlternativePaymentEvent.RedirectResult -> null // Ignore, handled by dynamic checkout events. } internal object DynamicCheckoutScreen { @@ -691,20 +744,22 @@ internal object DynamicCheckoutScreen { @Immutable data class Style( val sectionHeader: SectionHeaderStyle, + val subsectionTitle: POText.Style, val googlePayButton: GooglePayButton.Style, val expressPaymentButton: POBrandButtonStyle?, val regularPayment: RegularPaymentStyle, - val label: POText.Style, + val labeledContent: POLabeledContent.Style, + val groupedContent: POGroupedContent.Style, val field: POField.Style, val codeField: POField.Style, val radioField: PORadioField.Style, - val radioGroup: PORadioGroup.Style, // TODO: remove val checkbox: POCheckbox.Style, val dropdownMenu: PODropdownField.MenuStyle, val bodyText: AndroidTextView.Style, val errorText: POText.Style, - val messageBox: POMessageBox.Style, + val errorMessageBox: POMessageBox.Style, val dialog: PODialog.Style, + val stepper: POStepper.Style, val scanCardButton: POButton.Style, val actionsContainer: POActionsContainer.Style, val backgroundColor: Color, @@ -740,29 +795,32 @@ internal object DynamicCheckoutScreen { isLightTheme: Boolean ) = Style( sectionHeader = custom?.sectionHeader?.custom() ?: defaultSectionHeader, - googlePayButton = custom?.googlePayButton?.let { - GooglePayButton.custom(style = it, isLightTheme) - } ?: GooglePayButton.default(isLightTheme), - expressPaymentButton = custom?.expressPaymentButton, - regularPayment = custom?.regularPayment?.custom() ?: defaultRegularPayment, - label = custom?.label?.let { + subsectionTitle = custom?.subsectionTitle?.let { POText.custom(style = it) } ?: POText.Style( color = colors.text.primary, textStyle = typography.s14(FontWeight.Medium) ), + googlePayButton = custom?.googlePayButton?.let { + GooglePayButton.custom(style = it, isLightTheme) + } ?: GooglePayButton.default(isLightTheme), + expressPaymentButton = custom?.expressPaymentButton, + regularPayment = custom?.regularPayment?.custom() ?: defaultRegularPayment, + labeledContent = custom?.labeledContent?.let { + POLabeledContent.custom(style = it) + } ?: POLabeledContent.default, + groupedContent = custom?.groupedContent?.let { + POGroupedContent.custom(style = it) + } ?: POGroupedContent.default, field = custom?.field?.let { POField.custom(style = it) } ?: POField.default2, codeField = custom?.codeField?.let { POField.custom(style = it) - } ?: POCodeField.default, + } ?: POCodeField.default2, radioField = custom?.radioField?.let { PORadioField.custom(style = it) } ?: PORadioField.default, - radioGroup = custom?.radioButton?.let { - PORadioGroup.custom(style = it) - } ?: PORadioGroup.default, checkbox = custom?.checkbox?.let { POCheckbox.custom(style = it) } ?: POCheckbox.default2, @@ -782,12 +840,15 @@ internal object DynamicCheckoutScreen { color = colors.text.error, textStyle = typography.s14() ), - messageBox = custom?.messageBox?.let { + errorMessageBox = custom?.errorMessageBox?.let { POMessageBox.custom(style = it) - } ?: POMessageBox.error, + } ?: POMessageBox.error2, dialog = custom?.dialog?.let { PODialog.custom(style = it) } ?: PODialog.default, + stepper = custom?.stepper?.let { + POStepper.custom(style = it) + } ?: POStepper.default, scanCardButton = custom?.scanCardButton?.let { POButton.custom(style = it) } ?: CardTokenizationScreen.defaultScanButton, @@ -819,14 +880,14 @@ internal object DynamicCheckoutScreen { private val defaultRegularPayment: RegularPaymentStyle @Composable get() { val description = POText.Style( - color = colors.text.muted, - textStyle = typography.body2 + color = colors.text.secondary, + textStyle = typography.s14() ) return RegularPaymentStyle( title = POText.body1, description = POTextWithIcon.Style( text = description, - iconResId = R.drawable.po_icon_info, + iconResId = R.drawable.po_icon_warning_diamond, iconColorFilter = ColorFilter.tint(color = description.color) ), shape = shapes.roundedCornersSmall, @@ -963,7 +1024,7 @@ internal object DynamicCheckoutScreen { fun Style.cardTokenizationStyle() = CardTokenizationScreen.Style( title = regularPayment.title, - sectionTitle = label, + sectionTitle = subsectionTitle, field = field, radioField = radioField, dropdownMenu = dropdownMenu, @@ -977,6 +1038,29 @@ internal object DynamicCheckoutScreen { dragHandleColor = Color.Unspecified ) + @Composable + fun Style.nativeAlternativePaymentStyle() = NativeAlternativePaymentScreen.Style( + title = regularPayment.title, + bodyText = bodyText, + message = regularPayment.description.text, + labeledContent = labeledContent, + groupedContent = groupedContent, + field = field, + codeField = codeField, + radioField = radioField, + dropdownMenu = dropdownMenu, + checkbox = checkbox, + dialog = dialog, + stepper = stepper, + success = NativeAlternativePaymentScreen.defaultSuccess, + errorMessageBox = errorMessageBox, + actionsContainer = actionsContainer, + backgroundColor = Color.Unspecified, + progressIndicatorColor = Color.Unspecified, + dividerColor = Color.Unspecified, + dragHandleColor = Color.Unspecified + ) + val ShortAnimationDurationMillis = 300 val LongAnimationDurationMillis = 600 val CrossfadeAnimationDurationMillis = 400 @@ -984,20 +1068,7 @@ internal object DynamicCheckoutScreen { val RowComponentSpacing = 10.dp val PaymentLogoSize = 24.dp - val CaptureLogoHeight = 34.dp - - val CaptureImageWidth = 110.dp - val CaptureImageHeight = 140.dp val SuccessImageWidth = 220.dp val SuccessImageHeight = 280.dp - - private val ShortMessageMaxLength = 150 - - fun isMessageShort(text: String) = text.length <= ShortMessageMaxLength - - fun messageGravity(text: String): Int = - if (isMessageShort(text)) - Gravity.CENTER_HORIZONTAL - else Gravity.START } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt index 9081e7df4..f4321a73c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutViewModel.kt @@ -338,10 +338,7 @@ internal class DynamicCheckoutViewModel private constructor( ), content = if (selected) Content.NativeAlternativePayment(nativeAlternativePaymentState) else null, submitAction = if (selected && nativeAlternativePaymentState is NativeAlternativePaymentViewModelState.Loaded) - nativeAlternativePaymentState.primaryAction?.copy( - text = submitButtonText, - icon = configuration.submitButton.icon - ) else null + nativeAlternativePaymentState.primaryAction else null ) else -> null } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt index fa00ea4cb..315b601fc 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt @@ -43,7 +43,6 @@ import com.processout.sdk.ui.checkout.DynamicCheckoutSideEffect.* import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.* import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.Button import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.CancelButton -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.theme.ProcessOutTheme import com.processout.sdk.ui.googlepay.POGooglePayCardTokenizationLauncher @@ -140,8 +139,17 @@ class PODynamicCheckoutActivity : POBaseTransparentPortraitActivity() { invoiceId = configuration.invoiceRequest.invoiceId, gatewayConfigurationId = String() ), - submitButton = configuration.submitButton.map(), + header = null, + content = configuration.alternativePayment.content, + submitButton = configuration.submitButton.let { + PONativeAlternativePaymentConfiguration.Button( + text = it.text ?: getString(R.string.po_dynamic_checkout_button_pay), + icon = it.icon + ) + }, cancelButton = configuration.cancelButton?.map(), + inlineSingleSelectValuesLimit = configuration.alternativePayment.inlineSingleSelectValuesLimit, + barcode = configuration.alternativePayment.barcode, redirect = if (!returnUrl.isNullOrBlank()) RedirectConfiguration(returnUrl = returnUrl) else null, paymentConfirmation = PaymentConfirmationConfiguration( @@ -149,8 +157,6 @@ class PODynamicCheckoutActivity : POBaseTransparentPortraitActivity() { confirmButton = paymentConfirmation.confirmButton?.map(), cancelButton = paymentConfirmation.cancelButton?.map() ), - barcode = configuration.alternativePayment.barcode, - inlineSingleSelectValuesLimit = configuration.alternativePayment.inlineSingleSelectValuesLimit, success = null ) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutConfiguration.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutConfiguration.kt index 14c09df78..4b7beb247 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutConfiguration.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutConfiguration.kt @@ -13,6 +13,7 @@ import com.processout.sdk.ui.card.scanner.POCardScannerConfiguration import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.shared.image.PODrawableImage import com.processout.sdk.ui.core.style.* +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration import com.processout.sdk.ui.shared.configuration.POActionConfirmationConfiguration import com.processout.sdk.ui.shared.configuration.POBarcodeConfiguration import kotlinx.parcelize.Parcelize @@ -230,6 +231,7 @@ data class PODynamicCheckoutConfiguration( * Specifies alternative payment configuration. * * @param[returnUrl] Deep link return URL. Required for web authorization. + * @param[content] Custom content for native alternative payment. * @param[inlineSingleSelectValuesLimit] Defines maximum number of options that will be * displayed inline for parameters where user should select single option (e.g. radio buttons). * Default value is _5_. @@ -239,6 +241,7 @@ data class PODynamicCheckoutConfiguration( @Parcelize data class AlternativePaymentConfiguration( val returnUrl: String? = null, + val content: PONativeAlternativePaymentConfiguration.Content? = null, val inlineSingleSelectValuesLimit: Int = 5, val barcode: POBarcodeConfiguration = POBarcodeConfiguration(saveButton = POBarcodeConfiguration.Button()), val paymentConfirmation: PaymentConfirmationConfiguration = PaymentConfirmationConfiguration() @@ -249,8 +252,6 @@ data class PODynamicCheckoutConfiguration( * * @param[timeoutSeconds] Amount of time (in seconds) to wait for final payment confirmation. * Default value is 3 minutes, while maximum value is 15 minutes. - * @param[showProgressIndicatorAfterSeconds] Show progress indicator during payment confirmation after provided delay (in seconds). - * Use _null_ to hide, this is a default behaviour. * @param[confirmButton] Confirm button configuration. * @param[cancelButton] Cancel button configuration. */ @@ -258,7 +259,6 @@ data class PODynamicCheckoutConfiguration( data class PaymentConfirmationConfiguration( @IntRange(from = 0, to = 15 * 60) val timeoutSeconds: Int = 3 * 60, - val showProgressIndicatorAfterSeconds: Int? = null, val confirmButton: Button? = null, val cancelButton: CancelButton? = CancelButton() ) : Parcelable @@ -326,20 +326,22 @@ data class PODynamicCheckoutConfiguration( * Specifies dynamic checkout style. * * @param[sectionHeader] Section header style. + * @param[subsectionTitle] Subsection title style. * @param[googlePayButton] Google Pay button style. * @param[expressPaymentButton] Branded express payment button style. * @param[regularPayment] Regular payment style. - * @param[label] Field label style. + * @param[labeledContent] Labeled content style, such as customer instructions. + * @param[groupedContent] Grouped content style, such as customer instructions. * @param[field] Field style. * @param[codeField] Code field style. * @param[radioField] Radio field style. - * @param[radioButton] Radio button style. * @param[checkbox] Checkbox style. * @param[dropdownMenu] Dropdown menu style. * @param[bodyText] Body text style. * @param[errorText] Error text style. - * @param[messageBox] Message box style. + * @param[errorMessageBox] Error message box style. * @param[dialog] Dialog style. + * @param[stepper] Multi-step progress view style. * @param[scanCardButton] Scan card button style. * @param[actionsContainer] Style of action buttons and their container. * @param[backgroundColorResId] Color resource ID for background. @@ -350,20 +352,22 @@ data class PODynamicCheckoutConfiguration( @Parcelize data class Style( val sectionHeader: SectionHeaderStyle? = null, + val subsectionTitle: POTextStyle? = null, val googlePayButton: POGooglePayButtonStyle? = null, val expressPaymentButton: POBrandButtonStyle? = null, val regularPayment: RegularPaymentStyle? = null, - val label: POTextStyle? = null, + val labeledContent: POLabeledContentStyle? = null, + val groupedContent: POGroupedContentStyle? = null, val field: POFieldStyle? = null, val codeField: POFieldStyle? = null, val radioField: PORadioFieldStyle? = null, - val radioButton: PORadioButtonStyle? = null, // TODO: remove val checkbox: POCheckboxStyle? = null, val dropdownMenu: PODropdownMenuStyle? = null, val bodyText: POTextStyle? = null, val errorText: POTextStyle? = null, - val messageBox: POMessageBoxStyle? = null, + val errorMessageBox: POMessageBoxStyle? = null, val dialog: PODialogStyle? = null, + val stepper: POStepperStyle? = null, val scanCardButton: POButtonStyle? = null, val actionsContainer: POActionsContainerStyle? = null, @ColorRes diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt index a39f875a3..42acf5cc1 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutLauncher.kt @@ -22,7 +22,7 @@ import com.processout.sdk.ui.card.tokenization.delegate.POCardTokenizationEligib import com.processout.sdk.ui.card.tokenization.delegate.toResponse import com.processout.sdk.ui.checkout.delegate.* import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi -import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/DynamicCheckoutAlternativePaymentDefaultValues.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/DynamicCheckoutAlternativePaymentDefaultValues.kt index efb0eafde..c12c065d0 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/DynamicCheckoutAlternativePaymentDefaultValues.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/DynamicCheckoutAlternativePaymentDefaultValues.kt @@ -2,16 +2,17 @@ package com.processout.sdk.ui.checkout.delegate import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod -import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodDefaultValuesResponse -import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodParameter +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement +import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesResponse +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentParameterValue import java.util.UUID internal data class DynamicCheckoutAlternativePaymentDefaultValuesRequest( override val uuid: UUID, val paymentMethod: PODynamicCheckoutPaymentMethod.AlternativePayment, - val parameters: List + val parameters: List ) : POEventDispatcher.Request internal fun DynamicCheckoutAlternativePaymentDefaultValuesRequest.toResponse( - defaultValues: Map -) = PONativeAlternativePaymentMethodDefaultValuesResponse(uuid, defaultValues) + defaultValues: Map +) = NativeAlternativePaymentDefaultValuesResponse(uuid, defaultValues) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/PODynamicCheckoutDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/PODynamicCheckoutDelegate.kt index 339cf3d55..28ebe416a 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/PODynamicCheckoutDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/delegate/PODynamicCheckoutDelegate.kt @@ -6,10 +6,11 @@ import com.processout.sdk.api.model.request.POInvoiceRequest import com.processout.sdk.api.model.response.POCardIssuerInformation import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod import com.processout.sdk.api.model.response.POInvoice -import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodParameter +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi -import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentParameterValue import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsEvent @@ -69,14 +70,14 @@ interface PODynamicCheckoutDelegate { ): String? = issuerInformation.scheme /** - * Allows to prefill default values for the given parameters during native alternative payment. - * Return a map where key is a [PONativeAlternativePaymentMethodParameter.key] and value is a custom default value. + * Allows to prefill default values for the given [parameters] during native alternative payment. + * Return a map of parameter keys to their custom default values. * It's not mandatory to provide default values for all parameters. */ suspend fun defaultValues( paymentMethod: PODynamicCheckoutPaymentMethod.AlternativePayment, - parameters: List - ): Map = emptyMap() + parameters: List + ): Map = emptyMap() /** * Allows to override default alternative payment configuration. diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/NativeAlternativePayment.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/NativeAlternativePayment.kt deleted file mode 100644 index b7396f71b..000000000 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/NativeAlternativePayment.kt +++ /dev/null @@ -1,491 +0,0 @@ -package com.processout.sdk.ui.checkout.screen - -import androidx.compose.animation.* -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.core.tween -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -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.graphics.Color -import androidx.lifecycle.Lifecycle -import com.processout.sdk.ui.checkout.DynamicCheckoutEvent -import com.processout.sdk.ui.checkout.DynamicCheckoutEvent.* -import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.ShortAnimationDurationMillis -import com.processout.sdk.ui.core.component.POCircularProgressIndicator -import com.processout.sdk.ui.core.component.PORequestFocus -import com.processout.sdk.ui.core.component.field.POField -import com.processout.sdk.ui.core.component.field.POFieldLabels -import com.processout.sdk.ui.core.component.field.code.POCodeField -import com.processout.sdk.ui.core.component.field.code.POLabeledCodeField -import com.processout.sdk.ui.core.component.field.dropdown.PODropdownField -import com.processout.sdk.ui.core.component.field.dropdown.POLabeledDropdownField -import com.processout.sdk.ui.core.component.field.radio.POLabeledRadioField -import com.processout.sdk.ui.core.component.field.radio.PORadioGroup -import com.processout.sdk.ui.core.component.field.text.POLabeledTextField -import com.processout.sdk.ui.core.state.POImmutableList -import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState -import com.processout.sdk.ui.shared.state.FieldState - -@Composable -internal fun NativeAlternativePayment( - id: String, - state: NativeAlternativePaymentViewModelState, - onEvent: (DynamicCheckoutEvent) -> Unit, - style: DynamicCheckoutScreen.Style -) { -// when (state) { -// is UserInput -> UserInput(id, state, onEvent, style) -// is Capture -> if (!state.isCaptured) { -// Capture(id, state, onEvent, style) -// } -// else -> {} -// } -} - -//@Composable -//private fun UserInput( -// id: String, -// state: UserInput, -// onEvent: (DynamicCheckoutEvent) -> Unit, -// style: DynamicCheckoutScreen.Style -//) { -// Column( -// verticalArrangement = Arrangement.spacedBy(spacing.extraLarge) -// ) { -// val lifecycleEvent = rememberLifecycleEvent() -// val labelsStyle = remember { -// POFieldLabels.Style( -// title = style.label, -// description = style.errorText -// ) -// } -// val isPrimaryActionEnabled = with(state.primaryAction) { enabled && !loading } -// state.fields.elements.forEach { field -> -// when (field) { -// is TextField -> TextField( -// id = id, -// state = field.state, -// onEvent = onEvent, -// lifecycleEvent = lifecycleEvent, -// focusedFieldId = state.focusedFieldId, -// isPrimaryActionEnabled = isPrimaryActionEnabled, -// fieldStyle = style.field, -// labelsStyle = labelsStyle, -// modifier = Modifier.fillMaxWidth() -// ) -// is CodeField -> CodeField( -// id = id, -// state = field.state, -// onEvent = onEvent, -// lifecycleEvent = lifecycleEvent, -// focusedFieldId = state.focusedFieldId, -// isPrimaryActionEnabled = isPrimaryActionEnabled, -// fieldStyle = style.codeField, -// labelsStyle = labelsStyle, -// horizontalAlignment = Alignment.Start -// ) -// is RadioField -> RadioField( -// id = id, -// state = field.state, -// onEvent = onEvent, -// radioGroupStyle = style.radioGroup, -// labelsStyle = labelsStyle -// ) -// is DropdownField -> DropdownField( -// id = id, -// state = field.state, -// onEvent = onEvent, -// fieldStyle = style.field, -// labelsStyle = labelsStyle, -// menuStyle = style.dropdownMenu, -// modifier = Modifier.fillMaxWidth() -// ) -// } -// } -// } -//} - -@Composable -private fun TextField( - id: String, - state: FieldState, - onEvent: (DynamicCheckoutEvent) -> Unit, - lifecycleEvent: Lifecycle.Event, - focusedFieldId: String?, - isPrimaryActionEnabled: Boolean, - fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, - modifier: Modifier = Modifier -) { - val focusRequester = remember { FocusRequester() } - POLabeledTextField( - value = state.value, - onValueChange = { - onEvent( - FieldValueChanged( - paymentMethodId = id, - fieldId = state.id, - value = state.inputFilter?.filter(it) ?: it - ) - ) - }, - title = state.label ?: String(), - description = state.description, - modifier = modifier - .focusRequester(focusRequester) - .onFocusChanged { - onEvent( - FieldFocusChanged( - paymentMethodId = id, - fieldId = state.id, - isFocused = it.isFocused - ) - ) - }, - fieldStyle = fieldStyle, - labelsStyle = labelsStyle, - enabled = state.enabled, - isError = state.isError, - forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholder = state.placeholder, - visualTransformation = state.visualTransformation, - keyboardOptions = state.keyboardOptions, - keyboardActions = POField.keyboardActions( - imeAction = state.keyboardOptions.imeAction, - actionId = state.keyboardActionId, - enabled = isPrimaryActionEnabled, - onClick = { - onEvent( - Action( - actionId = it, - paymentMethodId = id - ) - ) - } - ) - ) - if (state.id == focusedFieldId && lifecycleEvent == Lifecycle.Event.ON_RESUME) { - PORequestFocus(focusRequester, lifecycleEvent) - } -} - -@Composable -private fun CodeField( - id: String, - state: FieldState, - onEvent: (DynamicCheckoutEvent) -> Unit, - lifecycleEvent: Lifecycle.Event, - focusedFieldId: String?, - isPrimaryActionEnabled: Boolean, - fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, - horizontalAlignment: Alignment.Horizontal, - modifier: Modifier = Modifier -) { - POLabeledCodeField( - value = state.value, - onValueChange = { - onEvent( - FieldValueChanged( - paymentMethodId = id, - fieldId = state.id, - value = it - ) - ) - }, - title = state.label ?: String(), - description = state.description, - modifier = modifier - .onFocusChanged { - onEvent( - FieldFocusChanged( - paymentMethodId = id, - fieldId = state.id, - isFocused = it.isFocused - ) - ) - }, - fieldStyle = fieldStyle, - labelsStyle = labelsStyle, - length = state.length ?: POCodeField.LengthMax, - horizontalAlignment = horizontalAlignment, - enabled = state.enabled, - isError = state.isError, - isFocused = state.id == focusedFieldId, - lifecycleEvent = lifecycleEvent, - keyboardOptions = state.keyboardOptions, - keyboardActions = POField.keyboardActions( - imeAction = state.keyboardOptions.imeAction, - actionId = state.keyboardActionId, - enabled = isPrimaryActionEnabled, - onClick = { - onEvent( - Action( - actionId = it, - paymentMethodId = id - ) - ) - } - ) - ) -} - -@Composable -private fun RadioField( - id: String, - state: FieldState, - onEvent: (DynamicCheckoutEvent) -> Unit, - radioGroupStyle: PORadioGroup.Style, - labelsStyle: POFieldLabels.Style, - modifier: Modifier = Modifier -) { - POLabeledRadioField( - value = state.value, - onValueChange = { - onEvent( - FieldValueChanged( - paymentMethodId = id, - fieldId = state.id, - value = it - ) - ) - }, - availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.label ?: String(), - description = state.description, - modifier = modifier, - radioGroupStyle = radioGroupStyle, - labelsStyle = labelsStyle, - isError = state.isError - ) -} - -@Composable -private fun DropdownField( - id: String, - state: FieldState, - onEvent: (DynamicCheckoutEvent) -> Unit, - fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, - menuStyle: PODropdownField.MenuStyle, - modifier: Modifier = Modifier -) { - POLabeledDropdownField( - value = state.value, - onValueChange = { - onEvent( - FieldValueChanged( - paymentMethodId = id, - fieldId = state.id, - value = it - ) - ) - }, - availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.label ?: String(), - description = state.description, - modifier = modifier - .onFocusChanged { - onEvent( - FieldFocusChanged( - paymentMethodId = id, - fieldId = state.id, - isFocused = it.isFocused - ) - ) - }, - fieldStyle = fieldStyle, - labelsStyle = labelsStyle, - menuStyle = menuStyle, - isError = state.isError, - placeholder = state.placeholder - ) -} - -//@Composable -//private fun Capture( -// id: String, -// state: Capture, -// onEvent: (DynamicCheckoutEvent) -> Unit, -// style: DynamicCheckoutScreen.Style -//) { -// AnimatedVisibility( -// visibleState = remember { -// MutableTransitionState(initialState = false) -// .apply { targetState = true } -// }, -// enter = fadeIn(animationSpec = tween(durationMillis = LongAnimationDurationMillis)), -// exit = fadeOut(animationSpec = tween(durationMillis = LongAnimationDurationMillis)) -// ) { -// val withPaddingTop = isMessageShort(state.message) && -// state.logoUrl == null && state.title == null && -// !state.withProgressIndicator -// Column( -// modifier = Modifier.conditional( -// condition = withPaddingTop, -// modifier = { padding(top = spacing.extraLarge) } -// ), -// verticalArrangement = Arrangement.spacedBy(spacing.extraLarge), -// horizontalAlignment = Alignment.CenterHorizontally -// ) { -// CaptureHeader(state, style) -// if (state.withProgressIndicator) { -// AnimatedProgressIndicator(style.progressIndicatorColor) -// } -// AndroidTextView( -// text = state.message, -// style = style.bodyText, -// modifier = Modifier.fillMaxWidth(), -// gravity = messageGravity(state.message), -// selectable = true, -// linksClickable = true -// ) -// var showImage by remember { mutableStateOf(state.image != null) } -// if (showImage) { -// when (state.image) { -// is Image.Url -> AsyncImage( -// model = state.image.value, -// contentDescription = null, -// modifier = Modifier.requiredSize( -// width = CaptureImageWidth, -// height = CaptureImageHeight -// ), -// alignment = Alignment.Center, -// contentScale = ContentScale.Fit, -// onError = { -// showImage = false -// } -// ) -// is Image.Bitmap -> { -// val bitmap = state.image.value -// Image( -// bitmap = remember(bitmap) { bitmap.asImageBitmap() }, -// contentDescription = null, -// modifier = Modifier.requiredSize( -// width = CaptureImageWidth, -// height = CaptureImageHeight -// ), -// alignment = Alignment.Center, -// contentScale = ContentScale.Fit -// ) -// } -// else -> {} -// } -// } -// Column( -// verticalArrangement = Arrangement.spacedBy(spacing.small) -// ) { -// state.primaryAction?.let { action -> -// POButton( -// text = action.text, -// onClick = { -// onEvent( -// Action( -// actionId = action.id, -// paymentMethodId = id -// ) -// ) -// }, -// modifier = Modifier -// .fillMaxWidth() -// .requiredHeightIn(min = dimensions.interactiveComponentMinSize), -// style = style.actionsContainer.primary, -// icon = action.icon -// ) -// } -// state.saveBarcodeAction?.let { action -> -// POButton( -// text = action.text, -// onClick = { -// onEvent( -// Action( -// actionId = action.id, -// paymentMethodId = id -// ) -// ) -// }, -// modifier = Modifier -// .fillMaxWidth() -// .requiredHeightIn(min = dimensions.interactiveComponentMinSize), -// style = style.actionsContainer.secondary, -// icon = action.icon -// ) -// } -// } -// state.confirmationDialog?.let { dialog -> -// PODialog( -// title = dialog.title, -// message = dialog.message, -// confirmActionText = dialog.confirmActionText, -// dismissActionText = dialog.dismissActionText, -// onConfirm = { -// onEvent( -// DialogAction( -// actionId = dialog.id, -// paymentMethodId = id, -// isConfirmed = true -// ) -// ) -// }, -// onDismiss = { -// onEvent( -// DialogAction( -// actionId = dialog.id, -// paymentMethodId = id, -// isConfirmed = false -// ) -// ) -// }, -// style = style.dialog -// ) -// } -// } -// } -//} - -//@Composable -//private fun CaptureHeader( -// state: Capture, -// style: DynamicCheckoutScreen.Style -//) { -// var showLogo by remember { mutableStateOf(true) } -// if (showLogo) { -// AsyncImage( -// model = state.logoUrl, -// contentDescription = null, -// modifier = Modifier.requiredHeight(CaptureLogoHeight), -// contentScale = ContentScale.FillHeight, -// onError = { -// showLogo = false -// } -// ) -// } else if (state.title != null) { -// with(style.regularPayment.title) { -// POText( -// text = state.title, -// color = color, -// style = textStyle -// ) -// } -// } -//} - -@Composable -private fun AnimatedProgressIndicator( - progressIndicatorColor: Color -) { - AnimatedVisibility( - visibleState = remember { - MutableTransitionState(initialState = false) - .apply { targetState = true } - }, - enter = expandVertically() + fadeIn(animationSpec = tween(durationMillis = ShortAnimationDurationMillis)), - exit = shrinkVertically() + fadeOut(animationSpec = tween(durationMillis = ShortAnimationDurationMillis)) - ) { - POCircularProgressIndicator.Large(color = progressIndicatorColor) - } -} diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentContent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentContent.kt index 4f3df958b..9dc62ac22 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentContent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentContent.kt @@ -43,7 +43,6 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.* import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Element.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration import com.processout.sdk.ui.napm.screen.NativeAlternativePaymentScreen.ContentTransitionSpec -import com.processout.sdk.ui.napm.screen.NativeAlternativePaymentScreen.LabeledContentStyle import com.processout.sdk.ui.napm.screen.NativeAlternativePaymentScreen.SuccessStyle import com.processout.sdk.ui.shared.component.AndroidTextView import com.processout.sdk.ui.shared.component.rememberLifecycleEvent @@ -536,7 +535,7 @@ private fun PhoneNumberField( @Composable private fun CopyableMessage( message: CopyableMessage, - style: LabeledContentStyle, + style: POLabeledContent.Style, modifier: Modifier = Modifier ) { POLabeledContent( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentScreen.kt index 98a5b9ed6..e074d852e 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/screen/NativeAlternativePaymentScreen.kt @@ -42,7 +42,6 @@ import com.processout.sdk.ui.core.component.stepper.POStepper import com.processout.sdk.ui.core.state.POActionState import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.style.POAxis -import com.processout.sdk.ui.core.style.POLabeledContentStyle import com.processout.sdk.ui.core.theme.ProcessOutTheme import com.processout.sdk.ui.core.theme.ProcessOutTheme.colors import com.processout.sdk.ui.core.theme.ProcessOutTheme.shapes @@ -283,7 +282,7 @@ internal object NativeAlternativePaymentScreen { val title: POText.Style, val bodyText: AndroidTextView.Style, val message: POText.Style, - val labeledContent: LabeledContentStyle, + val labeledContent: POLabeledContent.Style, val groupedContent: POGroupedContent.Style, val field: POField.Style, val codeField: POField.Style, @@ -301,13 +300,6 @@ internal object NativeAlternativePaymentScreen { val dragHandleColor: Color ) - @Immutable - data class LabeledContentStyle( - val label: POText.Style, - val text: POText.Style, - val copyButton: POButton.Style - ) - @Immutable data class SuccessStyle( val title: POText.Style, @@ -337,9 +329,11 @@ internal object NativeAlternativePaymentScreen { POText.custom(style = it) } ?: POText.Style( color = colors.text.secondary, - textStyle = typography.s15() + textStyle = typography.s14() ), - labeledContent = custom?.labeledContent?.custom() ?: defaultLabeledContent, + labeledContent = custom?.labeledContent?.let { + POLabeledContent.custom(style = it) + } ?: POLabeledContent.default, groupedContent = custom?.groupedContent?.let { POGroupedContent.custom(style = it) } ?: POGroupedContent.default, @@ -386,28 +380,7 @@ internal object NativeAlternativePaymentScreen { ) } - private val defaultLabeledContent: LabeledContentStyle - @Composable get() = LabeledContentStyle( - label = POText.Style( - color = colors.text.placeholder, - textStyle = typography.s12(FontWeight.Medium) - ), - text = POText.Style( - color = colors.text.primary, - textStyle = typography.s15(FontWeight.Medium) - ), - copyButton = POCopyButton.default - ) - - @Composable - private fun POLabeledContentStyle.custom() = - LabeledContentStyle( - label = POText.custom(style = label), - text = POText.custom(style = text), - copyButton = defaultLabeledContent.copyButton - ) - - private val defaultSuccess: SuccessStyle + val defaultSuccess: SuccessStyle @Composable get() = SuccessStyle( title = POText.Style( color = colors.text.primary,