diff --git a/sdk/src/main/res/values-ar/strings.xml b/sdk/src/main/res/values-ar/strings.xml index 91311d4f8..ac6a36b68 100644 --- a/sdk/src/main/res/values-ar/strings.xml +++ b/sdk/src/main/res/values-ar/strings.xml @@ -27,6 +27,8 @@ إلغاء لقد دفعت تم + نسخ + تم النسخ! تم إرسال التفاصيل في انتظار التأكيد يرجى الانتظار حتى %s دقيقة diff --git a/sdk/src/main/res/values-fr/strings.xml b/sdk/src/main/res/values-fr/strings.xml index 64d8684cb..ec497cebe 100644 --- a/sdk/src/main/res/values-fr/strings.xml +++ b/sdk/src/main/res/values-fr/strings.xml @@ -27,6 +27,8 @@ Annuler J\'ai payé Terminé + Copier + Copié ! Détails envoyés En attente de confirmation Veuillez patienter jusqu\'à %s minutes diff --git a/sdk/src/main/res/values-pl/strings.xml b/sdk/src/main/res/values-pl/strings.xml index 8a3c6382b..04f1b44b1 100644 --- a/sdk/src/main/res/values-pl/strings.xml +++ b/sdk/src/main/res/values-pl/strings.xml @@ -27,6 +27,8 @@ Anuluj Płatność wykonana Gotowe + Skopiuj + Skopiowano! Szczegóły przesłane Oczekiwanie na potwierdzenie Poczekaj maksymalnie %s minut diff --git a/sdk/src/main/res/values-pt/strings.xml b/sdk/src/main/res/values-pt/strings.xml index d6aaa4f1f..918b28d72 100644 --- a/sdk/src/main/res/values-pt/strings.xml +++ b/sdk/src/main/res/values-pt/strings.xml @@ -27,6 +27,8 @@ Cancelar Já paguei Concluído + Copiar + Copiado! Detalhes enviados Aguardando confirmação Aguarde até %s minutos diff --git a/sdk/src/main/res/values/strings.xml b/sdk/src/main/res/values/strings.xml index 9b50a19f8..2b8821b97 100644 --- a/sdk/src/main/res/values/strings.xml +++ b/sdk/src/main/res/values/strings.xml @@ -27,6 +27,8 @@ Cancel I\'ve paid Done + Copy + Copied! Details submitted Waiting for confirmation Please wait for up to %s minutes diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCopyButton.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCopyButton.kt new file mode 100644 index 000000000..42169ef54 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCopyButton.kt @@ -0,0 +1,108 @@ +@file:Suppress("MayBeConstant") + +package com.processout.sdk.ui.core.component + +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import com.processout.sdk.ui.core.R +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POCopyButton.FadeAnimationDurationMillis +import com.processout.sdk.ui.core.component.POCopyButton.SizeAnimationDurationMillis +import com.processout.sdk.ui.core.shared.image.PODrawableImage +import com.processout.sdk.ui.core.shared.image.POImageRenderingMode +import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing +import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun POCopyButton( + textToCopy: String, + copyText: String, + copiedText: String, + modifier: Modifier = Modifier, + style: POButton.Style = POCopyButton.default, + copyIcon: PODrawableImage? = POCopyButton.CopyIcon, + copiedIcon: PODrawableImage? = POCopyButton.CopiedIcon, + iconSize: Dp = dimensions.iconSizeSmall +) { + val clipboardManager = LocalClipboardManager.current + var isCopied by remember { mutableStateOf(false) } + var timerJob by remember { mutableStateOf(null) } + val coroutineScope = rememberCoroutineScope() + AnimatedContent( + targetState = isCopied, + transitionSpec = { + fadeIn( + tween(durationMillis = FadeAnimationDurationMillis) + ) togetherWith fadeOut( + tween(durationMillis = FadeAnimationDurationMillis) + ) using SizeTransform { _, _ -> + tween(durationMillis = SizeAnimationDurationMillis) + } + } + ) { isCopiedAnimated -> + POButton( + text = if (isCopiedAnimated) copiedText else copyText, + onClick = { + clipboardManager.setText(AnnotatedString(textToCopy)) + isCopied = true + timerJob?.cancel() + timerJob = coroutineScope.launch { + delay(timeMillis = 2500) + isCopied = false + } + }, + modifier = modifier, + style = style, + icon = if (isCopiedAnimated) copiedIcon else copyIcon, + iconSize = iconSize + ) + } +} + +/** @suppress */ +@ProcessOutInternalApi +object POCopyButton { + + val default: POButton.Style + @Composable get() = POButton.secondary2.let { + it.copy( + normal = it.normal.copy( + text = it.normal.text.copy( + textStyle = typography.s13(FontWeight.Medium) + ), + paddingHorizontal = spacing.space10 + ), + disabled = it.disabled.copy( + text = it.disabled.text.copy( + textStyle = typography.s13(FontWeight.Medium) + ), + paddingHorizontal = spacing.space10 + ) + ) + } + + internal val CopyIcon = PODrawableImage( + resId = R.drawable.po_icon_copy, + renderingMode = POImageRenderingMode.TEMPLATE + ) + + internal val CopiedIcon = PODrawableImage( + resId = R.drawable.po_icon_check, + renderingMode = POImageRenderingMode.TEMPLATE + ) + + internal val FadeAnimationDurationMillis = 300 + internal val SizeAnimationDurationMillis = 400 +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POGroupedContent.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POGroupedContent.kt new file mode 100644 index 000000000..0fb21f86d --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POGroupedContent.kt @@ -0,0 +1,110 @@ +package com.processout.sdk.ui.core.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.state.POImmutableList +import com.processout.sdk.ui.core.style.POGroupedContentStyle +import com.processout.sdk.ui.core.theme.ProcessOutTheme.colors +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 + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun POGroupedContent( + items: POImmutableList<@Composable () -> Unit>, + modifier: Modifier = Modifier, + style: POGroupedContent.Style = POGroupedContent.default, + title: String? = null +) { + Column(modifier = modifier) { + if (!title.isNullOrBlank()) { + POText( + text = title, + modifier = Modifier.padding(bottom = spacing.space12), + color = style.title.color, + style = style.title.textStyle + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .border( + width = style.border.width, + color = style.border.color, + shape = style.shape + ) + .clip(shape = style.shape) + .background(style.backgroundColor) + .padding(style.border.width) + ) { + items.elements.forEachIndexed { index, item -> + Box(modifier = Modifier.padding(spacing.space16)) { + item() + } + if (index != items.elements.lastIndex) { + HorizontalDivider( + modifier = Modifier.padding(horizontal = spacing.space4), + thickness = 1.dp, + color = style.dividerColor + ) + } + } + } + } +} + +/** @suppress */ +@ProcessOutInternalApi +object POGroupedContent { + + @Immutable + data class Style( + val title: POText.Style, + val shape: Shape, + val border: POBorderStroke, + val dividerColor: Color, + val backgroundColor: Color + ) + + val default: Style + @Composable get() = Style( + title = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderDefault2), + dividerColor = colors.border.border1, + backgroundColor = colors.surface.default + ) + + @Composable + fun custom(style: POGroupedContentStyle) = Style( + title = POText.custom(style = style.title), + shape = RoundedCornerShape(size = style.border.radiusDp.dp), + border = POBorderStroke( + width = style.border.widthDp.dp, + color = colorResource(id = style.border.colorResId) + ), + dividerColor = colorResource(id = style.dividerColorResId), + backgroundColor = colorResource(id = style.backgroundColorResId) + ) +} 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 new file mode 100644 index 000000000..798320cf9 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POLabeledContent.kt @@ -0,0 +1,52 @@ +package com.processout.sdk.ui.core.component + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +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.theme.ProcessOutTheme.colors +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing +import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun POLabeledContent( + label: String, + modifier: Modifier = Modifier, + labelStyle: POText.Style = POText.Style( + color = colors.text.placeholder, + textStyle = typography.s12(FontWeight.Medium) + ), + trailingContent: @Composable () -> Unit = {}, + trailingContentAlignment: Alignment = Alignment.TopStart, + content: @Composable () -> Unit +) { + Row( + modifier = modifier.height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier + .fillMaxHeight() + .padding(end = spacing.space8) + .weight(1f, fill = false), + verticalArrangement = Arrangement.spacedBy(spacing.space6) + ) { + POText( + text = label, + color = labelStyle.color, + style = labelStyle.textStyle + ) + content() + } + Box( + modifier = Modifier.fillMaxHeight(), + contentAlignment = trailingContentAlignment + ) { + trailingContent() + } + } +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POGroupedContentStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POGroupedContentStyle.kt new file mode 100644 index 000000000..37e05ab5c --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POGroupedContentStyle.kt @@ -0,0 +1,15 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POGroupedContentStyle( + val title: POTextStyle, + val border: POBorderStyle, + @ColorRes + val dividerColorResId: Int, + @ColorRes + val backgroundColorResId: Int +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POLabeledContentStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POLabeledContentStyle.kt new file mode 100644 index 000000000..fce03201a --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POLabeledContentStyle.kt @@ -0,0 +1,10 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POLabeledContentStyle( + val label: POTextStyle, + val text: POTextStyle +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Colors.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Colors.kt index 182f24586..4f0a371aa 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Colors.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Colors.kt @@ -94,6 +94,7 @@ data class POColors( @Immutable data class Border( + val border1: Color, val border2: Color, val border4: Color ) @@ -164,6 +165,7 @@ val POLightColorPalette = POColors( toastError = Color(0xFFFDE3DE) ), border = Border( + border1 = Color(0x0F121314), border2 = Color(0x14121314), border4 = Color(0x29212222) ) @@ -234,6 +236,7 @@ val PODarkColorPalette = POColors( toastError = Color(0xFF511511) ), border = Border( + border1 = Color(0x14F6F8FB), border2 = Color(0x1FF6F8FB), border4 = Color(0x33F6F8FB) ) diff --git a/ui-core/src/main/res/drawable/po_icon_check.xml b/ui-core/src/main/res/drawable/po_icon_check.xml new file mode 100644 index 000000000..5dfaacb03 --- /dev/null +++ b/ui-core/src/main/res/drawable/po_icon_check.xml @@ -0,0 +1,13 @@ + + + diff --git a/ui-core/src/main/res/drawable/po_icon_copy.xml b/ui-core/src/main/res/drawable/po_icon_copy.xml new file mode 100644 index 000000000..26f419010 --- /dev/null +++ b/ui-core/src/main/res/drawable/po_icon_copy.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentScreen.kt index 39dcf8db4..4de228143 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import coil.compose.AsyncImage @@ -53,14 +54,17 @@ import com.processout.sdk.ui.core.state.POActionState import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.state.POPhoneNumberFieldState 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.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.NativeAlternativePaymentScreen.AnimationDurationMillis import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.ContentTransitionSpec +import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.LabeledContentStyle import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.LogoHeight import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.SuccessStyle import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.* @@ -164,7 +168,11 @@ private fun Header( modifier: Modifier = Modifier, withDragHandle: Boolean = true ) { - Box(modifier = modifier.fillMaxWidth()) { + Box( + modifier = modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + ) { if (withDragHandle) { PODragHandle( modifier = Modifier @@ -173,8 +181,16 @@ private fun Header( color = dragHandleColor ) } + var showLogo by remember { mutableStateOf(true) } + val logoUrl = logo?.let { + if (isLightTheme) { + it.lightUrl.raster + } else { + it.darkUrl?.raster ?: it.lightUrl.raster + } + } AnimatedVisibility( - visible = logo != null || !title.isNullOrBlank(), + visible = showLogo && !logoUrl.isNullOrBlank() || !title.isNullOrBlank(), enter = fadeIn(animationSpec = tween(durationMillis = AnimationDurationMillis)), exit = fadeOut(animationSpec = tween(durationMillis = AnimationDurationMillis)), ) { @@ -196,27 +212,29 @@ private fun Header( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - val logoUrl = logo?.let { - if (isLightTheme) { - it.lightUrl.raster - } else { - it.darkUrl?.raster ?: it.lightUrl.raster + Box { + if (showLogo && !logoUrl.isNullOrBlank()) { + AsyncImage( + model = logoUrl, + contentDescription = null, + modifier = Modifier + .requiredHeight(LogoHeight) + .padding(end = spacing.space16), + contentScale = ContentScale.FillHeight, + onError = { + showLogo = false + } + ) } } - AsyncImage( - model = logoUrl, - contentDescription = null, - modifier = Modifier - .weight(0.8f, fill = false) - .requiredHeight(LogoHeight), - contentScale = ContentScale.FillHeight - ) - if (title != null) { + if (!title.isNullOrBlank()) { POText( text = title, modifier = Modifier.weight(1f, fill = false), color = titleStyle.color, - style = titleStyle.textStyle + style = titleStyle.textStyle, + overflow = TextOverflow.Ellipsis, + maxLines = 2 ) } } @@ -339,13 +357,18 @@ private fun Elements( descriptionStyle = style.errorMessageBox, modifier = Modifier.fillMaxWidth() ) - is InstructionMessage -> AndroidTextView( + is Message -> AndroidTextView( text = element.value, style = style.bodyText, modifier = Modifier.fillMaxWidth(), selectable = true, linksClickable = true ) + is CopyableMessage -> CopyableMessage( + message = element, + style = style.labeledContent, + modifier = Modifier.fillMaxWidth() + ) is Image -> Image( image = element, isLightTheme = isLightTheme @@ -355,6 +378,14 @@ private fun Elements( onEvent = onEvent, style = style ) + is InstructionGroup -> InstructionGroup( + group = element, + onEvent = onEvent, + style = style, + isLightTheme = isLightTheme, + modifier = Modifier.fillMaxWidth() + ) + else -> {} } } } @@ -614,6 +645,35 @@ private fun PhoneNumberField( } } +@Composable +private fun CopyableMessage( + message: CopyableMessage, + style: LabeledContentStyle, + modifier: Modifier = Modifier +) { + POLabeledContent( + label = message.label, + labelStyle = style.label, + modifier = modifier, + trailingContent = { + POCopyButton( + textToCopy = message.value, + copyText = message.copyText, + copiedText = message.copiedText, + modifier = Modifier.requiredHeightIn(min = dimensions.buttonIconSizeSmall), + style = style.copyButton + ) + }, + trailingContentAlignment = Alignment.Center + ) { + POText( + text = message.value, + color = style.text.color, + style = style.text.textStyle + ) + } +} + @Composable private fun Image( image: Image, @@ -690,6 +750,65 @@ private fun Barcode( } } +@Composable +private fun InstructionGroup( + group: InstructionGroup, + onEvent: (NativeAlternativePaymentEvent) -> Unit, + style: NativeAlternativePaymentScreen.Style, + isLightTheme: Boolean, + modifier: Modifier = Modifier +) { + val instructions = group.instructions.elements + val items: List<@Composable () -> Unit> = instructions.mapNotNull { instruction -> + when (instruction) { + is Message -> { + { + AndroidTextView( + text = instruction.value, + style = style.bodyText, + modifier = Modifier.fillMaxWidth(), + selectable = true, + linksClickable = true + ) + } + } + is CopyableMessage -> { + { + CopyableMessage( + message = instruction, + style = style.labeledContent, + modifier = Modifier.fillMaxWidth() + ) + } + } + is Image -> { + { + Image( + image = instruction, + isLightTheme = isLightTheme + ) + } + } + is Barcode -> { + { + Barcode( + barcode = instruction, + onEvent = onEvent, + style = style + ) + } + } + else -> null + } + } + POGroupedContent( + title = group.label, + items = POImmutableList(items), + modifier = modifier, + style = style.groupedContent + ) +} + @Composable private fun SuccessContent( state: Stage.Completed, @@ -781,6 +900,8 @@ internal object NativeAlternativePaymentScreen { data class Style( val title: POText.Style, val bodyText: AndroidTextView.Style, + val labeledContent: LabeledContentStyle, + val groupedContent: POGroupedContent.Style, val field: POField.Style, val codeField: POField.Style, val radioField: PORadioField.Style, @@ -797,6 +918,13 @@ 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, @@ -822,6 +950,10 @@ internal object NativeAlternativePaymentScreen { controlsTintColor = controlsTintColor ?: colors.text.primary ) } ?: AndroidTextView.default, + labeledContent = custom?.labeledContent?.custom() ?: defaultLabeledContent, + groupedContent = custom?.groupedContent?.let { + POGroupedContent.custom(style = it) + } ?: POGroupedContent.default, field = custom?.field?.let { POField.custom(style = it) } ?: POField.default2, @@ -865,6 +997,27 @@ 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 @Composable get() = SuccessStyle( title = POText.Style( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt index ff86aae60..8b3de00ae 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt @@ -21,9 +21,10 @@ import com.processout.sdk.ui.core.state.POActionState.Confirmation import com.processout.sdk.ui.core.transformation.POPhoneNumberVisualTransformation import com.processout.sdk.ui.core.transformation.POPhoneNumberVisualTransformation.PhoneNumberFormat import com.processout.sdk.ui.napm.NativeAlternativePaymentInteractorState.* -import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Content +import com.processout.sdk.ui.napm.NativeAlternativePaymentInteractorState.Element +import com.processout.sdk.ui.napm.NativeAlternativePaymentInteractorState.Loading +import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.* import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Element.* -import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Stage import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.CancelButton import com.processout.sdk.ui.shared.extension.currentAppLocale import com.processout.sdk.ui.shared.extension.map @@ -206,7 +207,7 @@ internal class NativeAlternativePaymentViewModel private constructor( when (element) { is Element.Form -> element.form.parameterDefinitions.map(fields) is Element.Instruction -> listOf(element.instruction.map()) - is Element.InstructionGroup -> emptyList() // TODO(v2) + is Element.InstructionGroup -> listOf(element.map()) } } return POImmutableList(elements) @@ -474,14 +475,23 @@ internal class NativeAlternativePaymentViewModel private constructor( private fun Instruction.map(): NativeAlternativePaymentViewModelState.Element = when (this) { - is Instruction.Message -> InstructionMessage( - label = label, - value = value - ) + is Instruction.Message -> map() is Instruction.Image -> Image(value = value) is Instruction.Barcode -> map() } + private fun Instruction.Message.map() = + if (label == null) { + Message(value = value) + } else { + CopyableMessage( + label = label, + value = value, + copyText = app.getString(R.string.po_native_apm_copy_button_text), + copiedText = app.getString(R.string.po_native_apm_copied_button_text) + ) + } + private fun Instruction.Barcode.map() = Barcode( image = bitmap, @@ -509,6 +519,17 @@ internal class NativeAlternativePaymentViewModel private constructor( } else null ) + private fun Element.InstructionGroup.map() = + InstructionGroup( + label = label, + instructions = POImmutableList( + instructions.mapNotNull { + val mapped = it.map() + if (mapped is InstructionElement) mapped else null + } + ) + ) + private fun Invoice.price(): String? = try { val formatter = NumberFormat.getCurrencyInstance() diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModelState.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModelState.kt index c9e62e54b..01ed268a9 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModelState.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModelState.kt @@ -80,16 +80,33 @@ internal sealed interface NativeAlternativePaymentViewModelState { data class PhoneNumberField(val state: POPhoneNumberFieldState) : Element @Immutable - data class InstructionMessage(val label: String?, val value: String) : Element + data class Message(val value: String) : InstructionElement @Immutable - data class Image(val value: POImageResource) : Element + data class CopyableMessage( + val label: String, + val value: String, + val copyText: String, + val copiedText: String + ) : InstructionElement + + @Immutable + data class Image(val value: POImageResource) : InstructionElement @Immutable data class Barcode( val image: Bitmap, val saveBarcodeAction: POActionState, val confirmationDialog: ConfirmationDialogState? + ) : InstructionElement + + @Immutable + data class InstructionGroup( + val label: String?, + val instructions: POImmutableList ) : Element } + + @Immutable + interface InstructionElement : Element } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt index 627e2b891..162c48c11 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt @@ -388,7 +388,9 @@ data class PONativeAlternativePaymentConfiguration( * Custom style. * * @param[title] Title style. - * @param[bodyText] Body text style, such as customer instruction. + * @param[bodyText] Body text style, such as customer instructions. + * @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. @@ -409,6 +411,8 @@ data class PONativeAlternativePaymentConfiguration( data class Style( val title: POTextStyle? = null, val bodyText: POTextStyle? = null, + val labeledContent: POLabeledContentStyle? = null, + val groupedContent: POGroupedContentStyle? = null, val field: POFieldStyle? = null, val codeField: POFieldStyle? = null, val radioField: PORadioFieldStyle? = null,