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,