diff --git a/build.gradle b/build.gradle index eeef5433a..581dc29ea 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - androidGradlePluginVersion = '8.10.1' + androidGradlePluginVersion = '8.11.0' kotlinVersion = '2.1.20' kspVersion = '2.1.20-1.0.32' dokkaVersion = '1.9.20' diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt index 7572219e6..71b6ed1c4 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt @@ -78,13 +78,13 @@ sealed class PONativeAlternativePaymentElement { /** * Available parameter value. * - * @param[value] Parameter value. + * @param[key] Parameter value. * @param[label] Value display label. * @param[preselected] Indicates whether the value should be preselected by default. */ @JsonClass(generateAdapter = true) data class AvailableValue( - val value: String, + val key: String, val label: String, val preselected: Boolean ) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/nativeapm/NativeAlternativePaymentMethodViewModel.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/nativeapm/NativeAlternativePaymentMethodViewModel.kt index cb5ec8392..26fc5b2d5 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/nativeapm/NativeAlternativePaymentMethodViewModel.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/nativeapm/NativeAlternativePaymentMethodViewModel.kt @@ -697,8 +697,8 @@ internal class NativeAlternativePaymentMethodViewModel private constructor( private fun getInputHint(type: ParameterType) = when (type) { - EMAIL -> app.getString(R.string.po_native_apm_email_placeholder) - PHONE -> app.getString(R.string.po_native_apm_phone_placeholder) + EMAIL -> app.getString(R.string.po_native_apm_placeholder_email) + PHONE -> app.getString(R.string.po_native_apm_placeholder_phone) else -> null } diff --git a/sdk/src/main/res/values-ar/strings.xml b/sdk/src/main/res/values-ar/strings.xml index 7524ab245..17ba6a447 100644 --- a/sdk/src/main/res/values-ar/strings.xml +++ b/sdk/src/main/res/values-ar/strings.xml @@ -29,9 +29,9 @@ فشل حفظ الصورة لم نتمكن من حفظ الصورة. يرجى التحقق من أذوناتك أو محاولة التقاط لقطة شاشة كبديل. فهمت - بلد - أدخل رقم الهاتف - name@example.com + بلد + أدخل رقم الهاتف + name@example.com نجاح! تمت الموافقة على الدفع البيانات مطلوبة الرقم غير صحيح diff --git a/sdk/src/main/res/values-fr/strings.xml b/sdk/src/main/res/values-fr/strings.xml index 6b47a5e26..c969a6e67 100644 --- a/sdk/src/main/res/values-fr/strings.xml +++ b/sdk/src/main/res/values-fr/strings.xml @@ -29,9 +29,9 @@ Échec de l\'enregistrement de l\'image Nous n\'avons pas pu enregistrer l\'image. Veuillez vérifier vos permissions ou alternativement prendre une capture d\'écran. Compris - Pays - Entrez votre numéro de téléphone - nom@exemple.fr + Pays + Entrez votre numéro de téléphone + nom@exemple.fr Succès !\nPaiement confirmé. Paramètre requis. Numéro invalide. diff --git a/sdk/src/main/res/values-pl/strings.xml b/sdk/src/main/res/values-pl/strings.xml index fee0194b2..43013fafd 100644 --- a/sdk/src/main/res/values-pl/strings.xml +++ b/sdk/src/main/res/values-pl/strings.xml @@ -29,9 +29,9 @@ Nie udało się zapisać obrazu Sprawdź uprawnienia systemu lub zrób zrzut ekranu. Rozumiem - Kraj - Twój numer telefonu - imię@przykład.pl + Kraj + Twój numer telefonu + imię@przykład.pl Sukces!\nPłatność przyjęta. Parametr jest wmagany. Niepoprawny numer. diff --git a/sdk/src/main/res/values-pt/strings.xml b/sdk/src/main/res/values-pt/strings.xml index a69e1e9ba..9fce5156a 100644 --- a/sdk/src/main/res/values-pt/strings.xml +++ b/sdk/src/main/res/values-pt/strings.xml @@ -29,9 +29,9 @@ Falha na gravação da imagem Não conseguimos gravar a imagem. Por favor, verifique as suas permissões ou tire uma captura do ecrā como alternativa. Compreendi - País - Insira o seu número de telemóvel - nome@exemplo.pt + País + Insira o seu número de telemóvel + nome@exemplo.pt Successo!\nPagamento aprovado. Campo obrigatório. Número inválido. diff --git a/sdk/src/main/res/values/strings.xml b/sdk/src/main/res/values/strings.xml index 77be47966..a504d87a2 100644 --- a/sdk/src/main/res/values/strings.xml +++ b/sdk/src/main/res/values/strings.xml @@ -29,9 +29,9 @@ Image save failed We couldn\'t save the image. Please check your permissions or try taking a screenshot as an alternative. Got it - Country - Enter phone number - name@example.com + Country + Enter phone number + name@example.com Success!\nPayment approved. Parameter is required. Number is not valid. diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POActionsContainer.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POActionsContainer.kt index 21fdaba19..3ed89ea4b 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POActionsContainer.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POActionsContainer.kt @@ -136,6 +136,17 @@ object POActionsContainer { ) } + val default2: Style + @Composable get() = with(ProcessOutTheme) { + Style( + primary = POButton.primary2, + secondary = POButton.secondary2, + dividerColor = colors.border.subtle, + backgroundColor = colors.surface.default, + axis = POAxis.Vertical + ) + } + @Composable fun custom(style: POActionsContainerStyle) = with(style) { Style( diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt index 0a49cb1b9..88345a621 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -235,7 +236,7 @@ object POButton { ), disabled = StateStyle( text = POText.Style( - color = colors.text.disabled, + color = colors.text.onButtonDisabled, textStyle = typography.button ), shape = shapes.roundedCornersSmall, @@ -252,6 +253,38 @@ object POButton { ) } + val primary2: Style + @Composable get() = with(ProcessOutTheme) { + Style( + normal = StateStyle( + text = POText.Style( + color = colors.text.inverse, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 0.dp, color = Color.Transparent), + backgroundColor = colors.button.primaryBackgroundDefault, + elevation = 0.dp + ), + disabled = StateStyle( + text = POText.Style( + color = colors.text.onButtonDisabled, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 0.dp, color = Color.Transparent), + backgroundColor = colors.button.primaryBackgroundDisabled, + elevation = 0.dp + ), + highlighted = HighlightedStyle( + textColor = colors.text.inverse, + borderColor = Color.Transparent, + backgroundColor = colors.button.primaryBackgroundPressed + ), + progressIndicatorColor = colors.text.inverse + ) + } + val secondary: Style @Composable get() = with(ProcessOutTheme) { Style( @@ -261,23 +294,55 @@ object POButton { textStyle = typography.button ), shape = shapes.roundedCornersSmall, - border = POBorderStroke(width = 1.dp, color = colors.button.secondaryBorderDefault), + border = POBorderStroke(width = 0.dp, color = Color.Transparent), backgroundColor = colors.button.secondaryBackgroundDefault, elevation = 0.dp ), disabled = StateStyle( text = POText.Style( - color = colors.text.disabled, + color = colors.text.onButtonDisabled, textStyle = typography.button ), shape = shapes.roundedCornersSmall, - border = POBorderStroke(width = 1.dp, color = colors.button.secondaryBorderDisabled), + border = POBorderStroke(width = 0.dp, color = Color.Transparent), backgroundColor = colors.button.secondaryBackgroundDisabled, elevation = 0.dp ), highlighted = HighlightedStyle( textColor = colors.text.primary, - borderColor = colors.button.secondaryBorderPressed, + borderColor = Color.Transparent, + backgroundColor = colors.button.secondaryBackgroundPressed + ), + progressIndicatorColor = colors.text.primary + ) + } + + val secondary2: Style + @Composable get() = with(ProcessOutTheme) { + Style( + normal = StateStyle( + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 0.dp, color = Color.Transparent), + backgroundColor = colors.button.secondaryBackgroundDefault, + elevation = 0.dp + ), + disabled = StateStyle( + text = POText.Style( + color = colors.text.onButtonDisabled, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 0.dp, color = Color.Transparent), + backgroundColor = colors.button.secondaryBackgroundDisabled, + elevation = 0.dp + ), + highlighted = HighlightedStyle( + textColor = colors.text.primary, + borderColor = Color.Transparent, backgroundColor = colors.button.secondaryBackgroundPressed ), progressIndicatorColor = colors.text.primary @@ -299,12 +364,12 @@ object POButton { ), disabled = StateStyle( text = POText.Style( - color = colors.text.disabled, + color = colors.text.onButtonDisabled, textStyle = typography.button ), shape = shapes.roundedCornersSmall, border = POBorderStroke(width = 0.dp, color = Color.Transparent), - backgroundColor = Color.Transparent, + backgroundColor = colors.button.ghostBackgroundDisabled, elevation = 0.dp ), highlighted = HighlightedStyle( diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/PODialog.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/PODialog.kt index 7b766a9d1..1d6a41c05 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/PODialog.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/PODialog.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -30,6 +31,7 @@ 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 /** @suppress */ @ProcessOutInternalApi @@ -166,8 +168,14 @@ object PODialog { val default: Style @Composable get() = Style( - title = POText.title, - message = POText.body2, + title = POText.Style( + color = colors.text.primary, + textStyle = typography.s20(FontWeight.SemiBold) + ), + message = POText.Style( + color = colors.text.secondary, + textStyle = typography.paragraph.s16() + ), confirmButton = defaultButton, dismissButton = defaultButton, backgroundColor = colors.surface.default @@ -176,8 +184,22 @@ object PODialog { private val defaultButton: POButton.Style @Composable get() = with(POButton.ghost) { copy( - normal = normal.copy(paddingHorizontal = spacing.large), - disabled = disabled.copy(paddingHorizontal = spacing.large) + normal = normal.copy( + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + paddingHorizontal = spacing.space16 + ), + disabled = disabled.copy( + text = POText.Style( + color = colors.text.onButtonDisabled, + textStyle = typography.s15(FontWeight.Medium) + ), + shape = shapes.roundedCorners6, + paddingHorizontal = spacing.space16 + ) ) } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POIme.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POIme.kt index b3799bfc0..c629ebb54 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POIme.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POIme.kt @@ -12,11 +12,13 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi object POIme { @Composable - fun isImeVisibleAsState(): State { + fun isImeVisibleAsState( + policy: SnapshotMutationPolicy = neverEqualPolicy() + ): State { val isImeVisible = remember { mutableStateOf( value = false, - policy = neverEqualPolicy() + policy = policy ) } val view = LocalView.current.rootView diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POMessageBox.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POMessageBox.kt index 11e546e3f..90853fb12 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POMessageBox.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POMessageBox.kt @@ -18,13 +18,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter 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.R import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POText.Style import com.processout.sdk.ui.core.style.POMessageBoxStyle 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 @@ -33,7 +36,7 @@ fun POMessageBox( text: String?, modifier: Modifier = Modifier, style: POMessageBox.Style = POMessageBox.error, - horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, + horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(spacing.space8), enterAnimationDelayMillis: Int = 0 ) { AnimatedVisibility( @@ -51,7 +54,10 @@ fun POMessageBox( ) .clip(style.shape) .background(color = style.backgroundColor) - .padding(spacing.extraLarge) + .padding( + horizontal = spacing.space12, + vertical = spacing.space8 + ) ) { var currentText by remember { mutableStateOf(String()) } if (!text.isNullOrBlank()) { @@ -80,7 +86,9 @@ object POMessageBox { val error: Style @Composable get() = Style( - textWithIcon = POTextWithIcon.default, + textWithIcon = POTextWithIcon.default.copy( + iconResId = R.drawable.po_icon_warning_diamond + ), shape = shapes.roundedCornersSmall, border = POBorderStroke( width = 1.dp, @@ -89,13 +97,34 @@ object POMessageBox { backgroundColor = colors.surface.error ) + val error2: Style + @Composable get() { + val text = Style( + color = colors.text.onTipError, + textStyle = typography.s14(FontWeight.Medium) + ) + return Style( + textWithIcon = POTextWithIcon.Style( + text = text, + iconResId = R.drawable.po_icon_warning_diamond, + iconColorFilter = ColorFilter.tint(color = text.color) + ), + shape = shapes.roundedCorners8, + border = POBorderStroke( + width = 1.dp, + color = colors.input.borderDefault2 + ), + backgroundColor = colors.surface.toastError + ) + } + @Composable fun custom(style: POMessageBoxStyle) = with(style) { val text = POText.custom(style = text) Style( textWithIcon = POTextWithIcon.Style( text = text, - iconResId = iconResId ?: R.drawable.po_info_icon, + iconResId = iconResId ?: error.textWithIcon.iconResId, iconColorFilter = if (iconResId != null) null else ColorFilter.tint(color = text.color) ), 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 8de3faaf4..aa9f59a42 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_info_icon, + iconResId = R.drawable.po_icon_info, iconColorFilter = ColorFilter.tint(color = text.color) ) } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/POField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/POField.kt index 28c9221f5..4e4daf2c8 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/POField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/POField.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.colorResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDirection @@ -33,6 +34,9 @@ import com.processout.sdk.ui.core.extension.conditional import com.processout.sdk.ui.core.style.POFieldStateStyle import com.processout.sdk.ui.core.style.POFieldStyle 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.spacing +import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography /** @suppress */ @ProcessOutInternalApi @@ -48,6 +52,7 @@ object POField { @Immutable data class StateStyle( val text: POText.Style, + val label: POText.Style, val placeholderTextColor: Color, val backgroundColor: Color, val controlsTintColor: Color, @@ -61,6 +66,7 @@ object POField { Style( normal = StateStyle( text = POText.body2, + label = POText.label1, placeholderTextColor = colors.text.muted, backgroundColor = colors.input.backgroundDefault, controlsTintColor = colors.text.primary, @@ -70,6 +76,10 @@ object POField { ), error = StateStyle( text = POText.body2, + label = POText.Style( + color = colors.text.error, + textStyle = typography.label1 + ), placeholderTextColor = colors.text.muted, backgroundColor = colors.input.backgroundDefault, controlsTintColor = colors.text.primary, @@ -79,6 +89,7 @@ object POField { ), focused = StateStyle( text = POText.body2, + label = POText.label1, placeholderTextColor = colors.text.muted, backgroundColor = colors.input.backgroundDefault, controlsTintColor = colors.text.primary, @@ -89,24 +100,106 @@ object POField { ) } + val default2: Style + @Composable get() = with(ProcessOutTheme) { + Style( + normal = StateStyle( + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + label = POText.Style( + color = colors.text.placeholder, + textStyle = typography.s15(FontWeight.Medium) + ), + placeholderTextColor = colors.text.placeholder, + backgroundColor = colors.input.backgroundDefault, + controlsTintColor = colors.text.primary, + dropdownRippleColor = colors.text.muted, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderDefault2) + ), + error = StateStyle( + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + label = POText.Style( + color = colors.text.error, + textStyle = typography.s15(FontWeight.Medium) + ), + placeholderTextColor = colors.text.placeholder, + backgroundColor = colors.input.backgroundDefault, + controlsTintColor = colors.text.primary, + dropdownRippleColor = colors.text.muted, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderError) + ), + focused = StateStyle( + text = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + label = POText.Style( + color = colors.text.placeholder, + textStyle = typography.s15(FontWeight.Medium) + ), + placeholderTextColor = colors.text.placeholder, + backgroundColor = colors.input.backgroundDefault, + controlsTintColor = colors.text.primary, + dropdownRippleColor = colors.text.muted, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.text.primary) + ) + ) + } + @Composable fun custom(style: POFieldStyle): Style { val normal = style.normal.toStateStyle() - return Style( + var customStyle = Style( normal = normal, error = style.error.toStateStyle(), focused = style.focused?.toStateStyle() ?: normal ) + if (customStyle.normal.label.color == Color.Unspecified) { + customStyle = customStyle.copy( + normal = customStyle.normal.copy( + label = default.normal.label + ) + ) + } + if (customStyle.error.label.color == Color.Unspecified) { + customStyle = customStyle.copy( + error = customStyle.error.copy( + label = default.error.label + ) + ) + } + if (customStyle.focused.label.color == Color.Unspecified) { + customStyle = customStyle.copy( + focused = customStyle.focused.copy( + label = default.focused.label + ) + ) + } + return customStyle } @Composable private fun POFieldStateStyle.toStateStyle() = StateStyle( text = POText.custom(style = text), + label = if (label.colorResId != 0) + POText.custom(style = label) else + POText.Style( + color = Color.Unspecified, + textStyle = typography.s15(FontWeight.Medium) + ), placeholderTextColor = colorResource(id = placeholderTextColorResId), backgroundColor = colorResource(id = backgroundColorResId), controlsTintColor = colorResource(id = controlsTintColorResId), dropdownRippleColor = dropdownRippleColorResId?.let { colorResource(id = it) } - ?: ProcessOutTheme.colors.text.muted, + ?: colors.text.muted, shape = RoundedCornerShape(size = border.radiusDp.dp), border = POBorderStroke( width = border.widthDp.dp, @@ -116,8 +209,14 @@ object POField { val contentPadding: PaddingValues @Composable get() = PaddingValues( - horizontal = ProcessOutTheme.spacing.large, - vertical = ProcessOutTheme.spacing.medium + horizontal = spacing.large, + vertical = spacing.medium + ) + + val contentPadding2: PaddingValues + @Composable get() = PaddingValues( + horizontal = spacing.space12, + vertical = spacing.space6 ) internal fun Style.stateStyle( diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckbox.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckbox.kt index 671b4e905..ad64da76e 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckbox.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckbox.kt @@ -3,29 +3,34 @@ package com.processout.sdk.ui.core.component.field.checkbox import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxColors +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.component.POText import com.processout.sdk.ui.core.component.POText.measuredPaddingTop -import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.CheckboxScale -import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.CheckboxSize +import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.MaterialCheckboxSize import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.colors -import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.textStyle +import com.processout.sdk.ui.core.component.field.checkbox.POCheckbox.stateStyle import com.processout.sdk.ui.core.style.POCheckboxStateStyle import com.processout.sdk.ui.core.style.POCheckboxStyle import com.processout.sdk.ui.core.style.POCheckmarkStyle 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 /** @suppress */ @@ -37,15 +42,23 @@ fun POCheckbox( onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, minHeight: Dp = dimensions.formComponentMinHeight, + checkboxSize: Dp = MaterialCheckboxSize, + rowShape: CornerBasedShape = shapes.roundedCorners4, style: POCheckbox.Style = POCheckbox.default, enabled: Boolean = true, isError: Boolean = false, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { + val stateStyle = style.stateStyle( + checked = checked, + enabled = enabled, + isError = isError + ) Row( modifier = modifier .fillMaxWidth() .requiredHeightIn(min = minHeight) + .clip(shape = rowShape) .clickable( onClick = { if (enabled) { @@ -53,41 +66,35 @@ fun POCheckbox( } }, interactionSource = interactionSource, - indication = null + indication = stateStyle.rippleColor?.let { ripple(color = it) } ) ) { + val checkboxScale = checkboxSize.value / MaterialCheckboxSize.value Checkbox( checked = checked, onCheckedChange = onCheckedChange, modifier = Modifier - .scale(CheckboxScale) - .requiredWidth(CheckboxSize) - .requiredHeight(minHeight) - .offset(x = (-0.5).dp), + .scale(checkboxScale) + .requiredWidth(checkboxSize) + .requiredHeight(minHeight), enabled = enabled, - colors = colors( - style = style, + colors = style.colors( enabled = enabled, isError = isError ) ) - val textStyle = textStyle( - style = style, - checked = checked, - enabled = enabled, - isError = isError - ) POText( text = text, modifier = Modifier.padding( - start = 10.dp, + start = spacing.space10, top = measuredPaddingTop( - textStyle = textStyle.textStyle, + textStyle = stateStyle.text.textStyle, componentHeight = minHeight - ) + ), + bottom = spacing.space10 ), - color = textStyle.color, - style = textStyle.textStyle + color = stateStyle.text.color, + style = stateStyle.text.textStyle ) } } @@ -107,7 +114,8 @@ object POCheckbox { @Immutable data class StateStyle( val checkmark: CheckmarkStyle, - val text: POText.Style + val text: POText.Style, + val rippleColor: Color? ) @Immutable @@ -125,7 +133,8 @@ object POCheckbox { borderColor = colors.input.borderDefault, backgroundColor = colors.surface.default ), - text = POText.label1 + text = POText.label1, + rippleColor = colors.surface.darkoutRipple ), selected = StateStyle( checkmark = CheckmarkStyle( @@ -133,7 +142,8 @@ object POCheckbox { borderColor = colors.button.primaryBackgroundDefault, backgroundColor = colors.button.primaryBackgroundDefault ), - text = POText.label1 + text = POText.label1, + rippleColor = colors.surface.darkoutRipple ), error = StateStyle( checkmark = CheckmarkStyle( @@ -141,7 +151,8 @@ object POCheckbox { borderColor = colors.input.borderError, backgroundColor = colors.surface.default ), - text = POText.label1 + text = POText.label1, + rippleColor = colors.surface.darkoutRipple ), disabled = StateStyle( checkmark = CheckmarkStyle( @@ -152,7 +163,60 @@ object POCheckbox { text = POText.Style( color = colors.text.disabled, textStyle = typography.label1 - ) + ), + rippleColor = null + ) + ) + + val default2: Style + @Composable get() = Style( + normal = StateStyle( + checkmark = CheckmarkStyle( + color = colors.checkRadio.iconDefault, + borderColor = colors.checkRadio.borderDefault, + backgroundColor = colors.checkRadio.surfaceDefault + ), + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + rippleColor = colors.surface.darkoutRipple + ), + selected = StateStyle( + checkmark = CheckmarkStyle( + color = colors.checkRadio.iconActive, + borderColor = colors.checkRadio.borderActive, + backgroundColor = colors.checkRadio.surfaceActive + ), + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + rippleColor = colors.surface.darkoutRipple + ), + error = StateStyle( + checkmark = CheckmarkStyle( + color = colors.checkRadio.iconError, + borderColor = colors.checkRadio.borderError, + backgroundColor = colors.checkRadio.surfaceError + ), + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + rippleColor = colors.surface.darkoutRipple + ), + disabled = StateStyle( + checkmark = CheckmarkStyle( + color = colors.checkRadio.iconDisabled, + borderColor = colors.checkRadio.borderDisabled, + backgroundColor = colors.checkRadio.surfaceDisabled + ), + text = POText.Style( + color = colors.text.disabled, + textStyle = typography.s15(FontWeight.Medium) + ), + rippleColor = null ) ) @@ -167,7 +231,8 @@ object POCheckbox { @Composable private fun POCheckboxStateStyle.toStateStyle() = StateStyle( checkmark = checkmark.toCheckmarkStyle(), - text = POText.custom(style = text) + text = POText.custom(style = text), + rippleColor = rippleColorResId?.let { colorResource(id = it) } ) @Composable @@ -177,12 +242,9 @@ object POCheckbox { backgroundColor = colorResource(id = backgroundColorResId) ) - private val MaterialCheckboxSize = 20.dp - internal val CheckboxSize = 22.dp - internal val CheckboxScale = CheckboxSize.value / MaterialCheckboxSize.value + internal val MaterialCheckboxSize = 20.dp - internal fun colors( - style: Style, + internal fun Style.colors( enabled: Boolean, isError: Boolean ): CheckboxColors { @@ -193,22 +255,22 @@ object POCheckbox { val checkedBorderColor: Color val checkedBoxColor: Color if (isError) { - with(style.error.checkmark) { + with(error.checkmark) { uncheckedCheckmarkColor = this.color uncheckedBorderColor = this.borderColor uncheckedBoxColor = this.backgroundColor - checkedCheckmarkColor = this.color + checkedCheckmarkColor = if (enabled) this.color else disabled.checkmark.color checkedBorderColor = this.borderColor checkedBoxColor = this.backgroundColor } } else { - with(style.normal.checkmark) { - uncheckedCheckmarkColor = if (enabled) this.color else style.disabled.checkmark.color + with(normal.checkmark) { + uncheckedCheckmarkColor = if (enabled) this.color else disabled.checkmark.color uncheckedBorderColor = this.borderColor uncheckedBoxColor = this.backgroundColor } - with(style.selected.checkmark) { - checkedCheckmarkColor = if (enabled) this.color else style.disabled.checkmark.color + with(selected.checkmark) { + checkedCheckmarkColor = if (enabled) this.color else disabled.checkmark.color checkedBorderColor = this.borderColor checkedBoxColor = this.backgroundColor } @@ -220,23 +282,22 @@ object POCheckbox { checkedCheckmarkColor = checkedCheckmarkColor, checkedBorderColor = checkedBorderColor, checkedBoxColor = checkedBoxColor, - disabledUncheckedBorderColor = style.disabled.checkmark.borderColor, - disabledUncheckedBoxColor = style.disabled.checkmark.backgroundColor, - disabledBorderColor = style.disabled.checkmark.borderColor, - disabledCheckedBoxColor = style.disabled.checkmark.backgroundColor, - disabledIndeterminateBorderColor = style.disabled.checkmark.borderColor, - disabledIndeterminateBoxColor = style.disabled.checkmark.backgroundColor + disabledUncheckedBorderColor = disabled.checkmark.borderColor, + disabledUncheckedBoxColor = disabled.checkmark.backgroundColor, + disabledBorderColor = disabled.checkmark.borderColor, + disabledCheckedBoxColor = disabled.checkmark.backgroundColor, + disabledIndeterminateBorderColor = disabled.checkmark.borderColor, + disabledIndeterminateBoxColor = disabled.checkmark.backgroundColor ) } - internal fun textStyle( - style: Style, + internal fun Style.stateStyle( checked: Boolean, enabled: Boolean, isError: Boolean - ): POText.Style = - if (!enabled) style.disabled.text - else if (isError) style.error.text - else if (checked) style.selected.text - else style.normal.text + ): StateStyle = + if (!enabled) disabled + else if (isError) error + else if (checked) selected + else normal } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POLabeledCheckboxField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckboxField.kt similarity index 61% rename from ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POLabeledCheckboxField.kt rename to ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckboxField.kt index 6cc3ae6a6..01f5227d6 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POLabeledCheckboxField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/checkbox/POCheckboxField.kt @@ -1,45 +1,46 @@ package com.processout.sdk.ui.core.component.field.checkbox import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi -import com.processout.sdk.ui.core.component.field.LabeledFieldLayout -import com.processout.sdk.ui.core.component.field.POFieldLabels +import com.processout.sdk.ui.core.component.POMessageBox +import com.processout.sdk.ui.core.theme.ProcessOutTheme.shapes /** @suppress */ @ProcessOutInternalApi @Composable -fun POLabeledCheckboxField( +fun POCheckboxField( text: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit, - title: String?, - description: String?, modifier: Modifier = Modifier, - checkboxStyle: POCheckbox.Style = POCheckbox.default, - labelsStyle: POFieldLabels.Style = POFieldLabels.default, + checkboxStyle: POCheckbox.Style = POCheckbox.default2, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, enabled: Boolean = true, isError: Boolean = false, + description: String? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - LabeledFieldLayout( - title = title, - description = description, - style = labelsStyle - ) { + Column(modifier = modifier) { POCheckbox( text = text, checked = checked, onCheckedChange = onCheckedChange, - modifier = modifier, - minHeight = 30.dp, + minHeight = 40.dp, + checkboxSize = 16.dp, + rowShape = shapes.roundedCorners6, style = checkboxStyle, enabled = enabled, isError = isError, interactionSource = interactionSource ) + POMessageBox( + text = description, + style = descriptionStyle + ) } } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField.kt index 3e8196d6a..b6a20a416 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.input.key.* import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.* import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp @@ -25,14 +26,18 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.component.PORequestFocus +import com.processout.sdk.ui.core.component.POText import com.processout.sdk.ui.core.component.field.POField +import com.processout.sdk.ui.core.component.field.code.POCodeField.align import com.processout.sdk.ui.core.component.field.code.POCodeField.rememberTextFieldWidth -import com.processout.sdk.ui.core.component.field.code.POCodeField.style import com.processout.sdk.ui.core.component.field.code.POCodeField.validLength import com.processout.sdk.ui.core.component.field.text.POTextField import com.processout.sdk.ui.core.component.texttoolbar.ProcessOutTextToolbar import com.processout.sdk.ui.core.state.POInputFilter -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.spacing +import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography /** @suppress */ @ProcessOutInternalApi @@ -53,7 +58,7 @@ fun POCodeField( keyboardActions: KeyboardActions = KeyboardActions.Default ) { var rowWidthPx by remember { mutableIntStateOf(0) } - val horizontalSpace = ProcessOutTheme.spacing.small + val horizontalSpace = spacing.small CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { Row( modifier = Modifier @@ -145,7 +150,7 @@ fun POCodeField( modifier = modifier .requiredWidth( rememberTextFieldWidth( - defaultWidth = ProcessOutTheme.dimensions.interactiveComponentMinSize, + defaultWidth = dimensions.interactiveComponentMinSize, rowWidth = with(LocalDensity.current) { rowWidthPx.toDp() }, space = horizontalSpace, length = validLength @@ -176,7 +181,7 @@ fun POCodeField( } }, contentPadding = PaddingValues(0.dp), - style = style(style), + style = align(style), enabled = enabled, isError = isError, keyboardOptions = keyboardOptions, @@ -235,30 +240,65 @@ private fun List.codeValue() = TextFieldValue( object POCodeField { val default: POField.Style - @Composable get() = with(POField.default) { - copy( - normal = normal.default(), - error = error.default(), - focused = focused.default() + @Composable get() = POField.default.let { + val text = POText.Style( + color = colors.text.primary, + textStyle = typography.title + ) + it.copy( + normal = it.normal.copy(text = text), + error = it.error.copy(text = text), + focused = it.focused.copy(text = text) ) } - internal fun style(style: POField.Style) = with(style) { - copy( - normal = normal.textAlignCenter(), - error = error.textAlignCenter(), - focused = focused.textAlignCenter() + val default2: POField.Style + @Composable get() = POField.default2.let { + val text = POText.Style( + color = colors.text.primary, + textStyle = typography.s20(FontWeight.Medium) + ) + it.copy( + normal = it.normal.copy( + text = text, + label = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ) + ), + error = it.error.copy( + text = text, + label = POText.Style( + color = colors.text.error, + textStyle = typography.s16(FontWeight.Medium) + ) + ), + focused = it.focused.copy( + text = text, + label = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ) + ) + ) + } + + internal fun align(style: POField.Style) = style.let { + it.copy( + normal = it.normal.textAlignCenter(), + error = it.error.textAlignCenter(), + focused = it.focused.textAlignCenter() ) } - @Composable - private fun POField.StateStyle.default() = copy( - text = text.copy(textStyle = ProcessOutTheme.typography.title) - ) - - private fun POField.StateStyle.textAlignCenter() = copy( - text = text.copy(textStyle = text.textStyle.copy(textAlign = TextAlign.Center)) - ) + private fun POField.StateStyle.textAlignCenter() = + copy( + text = text.copy( + textStyle = text.textStyle.copy( + textAlign = TextAlign.Center + ) + ) + ) val LengthMin = 1 val LengthMax = 8 diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField2.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField2.kt new file mode 100644 index 000000000..66c74caea --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/code/POCodeField2.kt @@ -0,0 +1,279 @@ +package com.processout.sdk.ui.core.component.field.code + +import androidx.compose.foundation.focusGroup +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.input.key.* +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.* +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POMessageBox +import com.processout.sdk.ui.core.component.PORequestFocus +import com.processout.sdk.ui.core.component.POText +import com.processout.sdk.ui.core.component.field.POField +import com.processout.sdk.ui.core.component.field.POField.stateStyle +import com.processout.sdk.ui.core.component.field.code.POCodeField.align +import com.processout.sdk.ui.core.component.field.code.POCodeField.rememberTextFieldWidth +import com.processout.sdk.ui.core.component.field.code.POCodeField.validLength +import com.processout.sdk.ui.core.component.field.text.POTextField2 +import com.processout.sdk.ui.core.component.texttoolbar.ProcessOutTextToolbar +import com.processout.sdk.ui.core.state.POInputFilter +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun POCodeField2( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier = Modifier, + textFieldModifier: Modifier = Modifier, + fieldStyle: POField.Style = POCodeField.default2, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, + length: Int = POCodeField.LengthMax, + label: String? = null, + description: String? = null, + enabled: Boolean = true, + isError: Boolean = false, + isFocused: Boolean = false, + lifecycleEvent: Lifecycle.Event? = null, + inputFilter: POInputFilter? = null, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default +) { + Column(modifier = modifier) { + if (!label.isNullOrBlank()) { + val fieldStateStyle = fieldStyle.stateStyle( + isError = isError, + isFocused = isFocused + ) + POText( + text = label, + modifier = Modifier.padding(bottom = spacing.space12), + color = fieldStateStyle.label.color, + style = fieldStateStyle.label.textStyle + ) + } + Code( + value = value, + onValueChange = onValueChange, + style = fieldStyle, + length = length, + enabled = enabled, + isError = isError, + isFocused = isFocused, + lifecycleEvent = lifecycleEvent, + inputFilter = inputFilter, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + modifier = textFieldModifier + ) + POMessageBox( + text = description, + modifier = Modifier.padding(top = spacing.space12), + style = descriptionStyle + ) + } +} + +@Composable +private fun Code( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + style: POField.Style, + length: Int, + enabled: Boolean, + isError: Boolean, + isFocused: Boolean, + lifecycleEvent: Lifecycle.Event?, + inputFilter: POInputFilter?, + keyboardOptions: KeyboardOptions, + keyboardActions: KeyboardActions, + modifier: Modifier = Modifier +) { + val validLength = remember(length) { validLength(length) } + var values by remember(validLength) { mutableStateOf(values(value.text, validLength, inputFilter)) } + var focusedIndex by remember(validLength) { mutableIntStateOf(values.focusedIndex()) } + val clipboardManager = LocalClipboardManager.current + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr, + LocalTextToolbar provides ProcessOutTextToolbar( + view = LocalView.current, + onPasteRequested = { + if (clipboardManager.hasText()) { + val pastedValues = values( + text = clipboardManager.getText()?.text ?: String(), + length = validLength, + inputFilter = inputFilter + ) + if (!pastedValues.all { it.text.isEmpty() }) { + values = pastedValues + focusedIndex = values.focusedIndex() + onValueChange(values.codeValue()) + } + } + }, + hideUnspecifiedActions = true + ) + ) { + var rowWidthPx by remember { mutableIntStateOf(0) } + val horizontalSpace = spacing.space8 + Row( + modifier = Modifier + .focusGroup() + .fillMaxWidth() + .onGloballyPositioned { rowWidthPx = it.size.width }, + horizontalArrangement = Arrangement.spacedBy(horizontalSpace), + verticalAlignment = Alignment.CenterVertically + ) { + val focusManager = LocalFocusManager.current + for (textFieldIndex in values.indices) { + val focusRequester = remember { FocusRequester() } + POTextField2( + value = values[textFieldIndex], + onValueChange = { updatedValue -> + if (updatedValue.selection.length == 0) { + val currentValue = values[textFieldIndex] + val updatedFilteredValue = inputFilter?.filter(updatedValue) ?: updatedValue + values = values.mapIndexed { index, textFieldValue -> + if (index == textFieldIndex) { + val updatedText = updatedFilteredValue.text.firstOrNull()?.toString() ?: String() + val isTextChanged = textFieldValue.text != updatedText + TextFieldValue( + text = updatedText, + selection = if (isTextChanged) { + TextRange(updatedText.length) + } else { + updatedFilteredValue.selection + } + ) + } else { + textFieldValue.copy(selection = TextRange.Zero) + } + } + if (textFieldIndex != values.lastIndex && + updatedFilteredValue.text.length == 2 && + updatedFilteredValue.selection.start == 2 + ) { + val nextText = updatedFilteredValue.text.last().toString() + values = values.mapIndexed { index, textFieldValue -> + if (index == textFieldIndex + 1) { + TextFieldValue( + text = nextText, + selection = TextRange(nextText.length) + ) + } else { + textFieldValue.copy(selection = TextRange.Zero) + } + } + } + val isSelectionChangedOnly = currentValue.text == updatedFilteredValue.text && + currentValue.selection != updatedFilteredValue.selection + if (updatedFilteredValue.text.isNotEmpty() && + !isSelectionChangedOnly && + textFieldIndex != values.lastIndex + ) { + focusedIndex = textFieldIndex + 1 + } + onValueChange(values.codeValue()) + } + }, + modifier = Modifier.requiredWidth( + rememberTextFieldWidth( + defaultWidth = 40.dp, + rowWidth = with(LocalDensity.current) { rowWidthPx.toDp() }, + space = horizontalSpace, + length = validLength + ) + ), + textFieldModifier = modifier + .onPreviewKeyEvent { + if (it.key == Key.Backspace && + it.type == KeyEventType.KeyDown && + textFieldIndex != 0 && + values[textFieldIndex].selection.start == 0 + ) { + values = values.mapIndexed { index, textFieldValue -> + if (index == textFieldIndex - 1) { + TextFieldValue() + } else { + textFieldValue.copy(selection = TextRange.Zero) + } + } + focusManager.moveFocus(FocusDirection.Previous) + onValueChange(values.codeValue()) + } + false + } + .focusRequester(focusRequester) + .onFocusChanged { + if (it.isFocused) { + focusedIndex = textFieldIndex + } + }, + contentPadding = PaddingValues(spacing.space0), + fieldStyle = align(style), + enabled = enabled, + isError = isError, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions + ) + if (isFocused && textFieldIndex == focusedIndex) { + if (lifecycleEvent == Lifecycle.Event.ON_RESUME) { + PORequestFocus(focusRequester, lifecycleEvent) + } else { + PORequestFocus(focusRequester) + } + } + } + } + } +} + +private fun values( + text: String, + length: Int, + inputFilter: POInputFilter? +): List { + val values = mutableListOf() + while (values.size < length) { + values.add(TextFieldValue()) + } + val filteredText = inputFilter?.filter(TextFieldValue(text = text))?.text ?: text + filteredText + .take(length) + .forEachIndexed { index, char -> + val value = char.toString() + values[index] = TextFieldValue( + text = value, + selection = TextRange(value.length) + ) + } + return values +} + +private fun List.focusedIndex(): Int { + forEachIndexed { index, textFieldValue -> + if (textFieldValue.text.isEmpty()) { + return index + } + } + return lastIndex +} + +private fun List.codeValue() = TextFieldValue( + text = joinToString(separator = String()) { it.text } +) diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField.kt index dd1707364..4475c289e 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField.kt @@ -17,8 +17,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color +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 import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -26,6 +28,7 @@ import androidx.compose.ui.window.PopupProperties import com.processout.sdk.ui.core.R import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.component.POBorderStroke +import com.processout.sdk.ui.core.component.POIme.isImeVisibleAsState import com.processout.sdk.ui.core.component.POText import com.processout.sdk.ui.core.component.field.POField import com.processout.sdk.ui.core.component.field.POField.stateStyle @@ -33,7 +36,11 @@ import com.processout.sdk.ui.core.component.field.text.POTextField import com.processout.sdk.ui.core.state.POAvailableValue import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.style.PODropdownMenuStyle -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 /** @suppress */ @ProcessOutInternalApi @@ -43,25 +50,39 @@ fun PODropdownField( onValueChange: (TextFieldValue) -> Unit, availableValues: POImmutableList, modifier: Modifier = Modifier, - fieldContentPadding: PaddingValues = POField.contentPadding, + contentPadding: PaddingValues = POField.contentPadding, fieldStyle: POField.Style = POField.default, menuStyle: PODropdownField.MenuStyle = PODropdownField.defaultMenu, menuMatchesTextFieldWidth: Boolean = true, preferFormattedTextSelection: Boolean = false, enabled: Boolean = true, isError: Boolean = false, - placeholderText: String? = null + placeholder: String? = null ) { MaterialTheme( colorScheme = MaterialTheme.colorScheme.copy(surface = Color.Transparent), shapes = MaterialTheme.shapes.copy(extraSmall = menuStyle.shape) ) { var expanded by remember { mutableStateOf(false) } + var expanding by remember { mutableStateOf(false) } + val isImeVisible by isImeVisibleAsState(policy = structuralEqualityPolicy()) + if (expanding && isImeVisible) { + LocalFocusManager.current.clearFocus(force = true) + } + LaunchedEffect(expanding, isImeVisible) { + if (expanding && !isImeVisible) { + expanding = false + expanded = true + } + } ExposedDropdownMenuBox( expanded = expanded, onExpandedChange = { if (enabled) { - expanded = it + when (it) { + true -> expanding = true + false -> expanded = false + } } } ) { @@ -81,13 +102,13 @@ fun PODropdownField( .onFocusChanged { isFocused = it.isFocused }, - contentPadding = fieldContentPadding, + contentPadding = contentPadding, style = fieldStyle, enabled = enabled, readOnly = true, isDropdown = true, isError = isError, - placeholderText = placeholderText, + placeholder = placeholder, trailingIcon = { Icon( painter = painterResource(id = R.drawable.po_dropdown_arrow), @@ -97,8 +118,8 @@ fun PODropdownField( ) } ) - val menuItemHeight = ProcessOutTheme.dimensions.formComponentMinHeight - val menuVerticalPaddings = ProcessOutTheme.spacing.large + val menuItemHeight = dimensions.formComponentMinHeight + val menuVerticalPaddings = spacing.large val maxMenuHeight = remember { menuItemHeight * PODropdownField.MaxVisibleMenuItems + menuVerticalPaddings } DropdownMenu( expanded = expanded, @@ -150,7 +171,7 @@ private fun MenuItem( indication = ripple(color = style.rippleColor) ) .fillMaxWidth() - .padding(horizontal = ProcessOutTheme.spacing.large), + .padding(horizontal = spacing.large), contentAlignment = Alignment.CenterStart ) { POText( @@ -177,15 +198,25 @@ object PODropdownField { ) val defaultMenu: MenuStyle - @Composable get() = with(ProcessOutTheme) { - MenuStyle( - text = POText.body2, - backgroundColor = colors.surface.neutral, - rippleColor = colors.text.muted, - shape = shapes.roundedCornersSmall, - border = POBorderStroke(width = 0.dp, color = Color.Transparent) - ) - } + @Composable get() = MenuStyle( + text = POText.body2, + backgroundColor = colors.surface.neutral, + rippleColor = colors.text.muted, + shape = shapes.roundedCornersSmall, + border = POBorderStroke(width = 0.dp, color = Color.Transparent) + ) + + val defaultMenu2: MenuStyle + @Composable get() = MenuStyle( + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + backgroundColor = colors.surface.neutral, + rippleColor = colors.text.muted, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 0.dp, color = Color.Transparent) + ) @Composable fun custom(style: PODropdownMenuStyle) = with(style) { @@ -201,5 +232,5 @@ object PODropdownField { ) } - internal val MaxVisibleMenuItems = 6 + internal val MaxVisibleMenuItems = 10 } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField2.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField2.kt new file mode 100644 index 000000000..305d417ef --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/PODropdownField2.kt @@ -0,0 +1,187 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package com.processout.sdk.ui.core.component.field.dropdown + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.window.PopupProperties +import com.processout.sdk.ui.core.R +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POIme.isImeVisibleAsState +import com.processout.sdk.ui.core.component.POMessageBox +import com.processout.sdk.ui.core.component.POText +import com.processout.sdk.ui.core.component.field.POField +import com.processout.sdk.ui.core.component.field.POField.stateStyle +import com.processout.sdk.ui.core.component.field.text.POTextField2 +import com.processout.sdk.ui.core.state.POAvailableValue +import com.processout.sdk.ui.core.state.POImmutableList +import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun PODropdownField2( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + availableValues: POImmutableList, + modifier: Modifier = Modifier, + textFieldModifier: Modifier = Modifier, + contentPadding: PaddingValues = POField.contentPadding2, + fieldStyle: POField.Style = POField.default2, + menuStyle: PODropdownField.MenuStyle = PODropdownField.defaultMenu2, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, + menuMatchesTextFieldWidth: Boolean = true, + preferFormattedTextSelection: Boolean = false, + enabled: Boolean = true, + isError: Boolean = false, + label: String? = null, + placeholder: String? = null, + description: String? = null +) { + MaterialTheme( + colorScheme = MaterialTheme.colorScheme.copy(surface = Color.Transparent), + shapes = MaterialTheme.shapes.copy(extraSmall = menuStyle.shape) + ) { + var expanded by remember { mutableStateOf(false) } + var expanding by remember { mutableStateOf(false) } + val isImeVisible by isImeVisibleAsState(policy = structuralEqualityPolicy()) + if (expanding && isImeVisible) { + LocalFocusManager.current.clearFocus(force = true) + } + LaunchedEffect(expanding, isImeVisible) { + if (expanding && !isImeVisible) { + expanding = false + expanded = true + } + } + ExposedDropdownMenuBox( + modifier = modifier, + expanded = expanded, + onExpandedChange = { + if (enabled) { + when (it) { + true -> expanding = true + false -> expanded = false + } + } + } + ) { + var isFocused by remember { mutableStateOf(false) } + val fieldStateStyle = fieldStyle.stateStyle(isError = isError, isFocused = isFocused) + POTextField2( + value = availableValues.elements.find { it.value == value.text } + ?.let { + TextFieldValue( + text = if (preferFormattedTextSelection && it.formattedText != null) + it.formattedText else it.text + ) + } ?: TextFieldValue(), + onValueChange = {}, + modifier = Modifier.fillMaxWidth(), + textFieldModifier = textFieldModifier + .menuAnchor(MenuAnchorType.PrimaryNotEditable) + .onFocusChanged { + isFocused = it.isFocused + }, + contentPadding = contentPadding, + fieldStyle = fieldStyle, + descriptionStyle = descriptionStyle, + enabled = enabled, + readOnly = true, + isDropdown = true, + isError = isError, + label = label, + placeholder = placeholder, + description = description, + trailingIcon = { + Icon( + painter = painterResource(id = R.drawable.po_chevron_down), + contentDescription = null, + modifier = Modifier + .scale(1.1f) + .rotate(if (expanded) 180f else 0f), + tint = fieldStateStyle.label.color + ) + } + ) + val menuItemHeight = dimensions.formComponentMinHeight + val menuVerticalPaddings = spacing.large + val maxMenuHeight = remember { menuItemHeight * PODropdownField.MaxVisibleMenuItems + menuVerticalPaddings } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .exposedDropdownSize(matchTextFieldWidth = menuMatchesTextFieldWidth) + .heightIn(max = maxMenuHeight) + .border( + width = menuStyle.border.width, + color = menuStyle.border.color, + shape = menuStyle.shape + ) + .background(color = menuStyle.backgroundColor), + properties = PopupProperties( + focusable = true, + dismissOnBackPress = true, + dismissOnClickOutside = true + ) + ) { + availableValues.elements.forEach { availableValue -> + MenuItem( + availableValue = availableValue, + onClick = { + expanded = false + onValueChange(TextFieldValue(it.value)) + }, + modifier = Modifier.requiredHeight(menuItemHeight), + style = menuStyle + ) + } + } + } + } +} + +@Composable +private fun MenuItem( + availableValue: POAvailableValue, + onClick: (POAvailableValue) -> Unit, + modifier: Modifier = Modifier, + style: PODropdownField.MenuStyle, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } +) { + Box( + modifier = modifier + .clickable( + onClick = { onClick(availableValue) }, + interactionSource = interactionSource, + indication = ripple(color = style.rippleColor) + ) + .fillMaxWidth() + .padding(horizontal = spacing.large), + contentAlignment = Alignment.CenterStart + ) { + POText( + text = availableValue.text, + color = style.text.color, + style = style.text.textStyle, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/POLabeledDropdownField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/POLabeledDropdownField.kt index a28901455..e758566cc 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/POLabeledDropdownField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/dropdown/POLabeledDropdownField.kt @@ -27,7 +27,7 @@ fun POLabeledDropdownField( preferFormattedTextSelection: Boolean = false, enabled: Boolean = true, isError: Boolean = false, - placeholderText: String? = null + placeholder: String? = null ) { LabeledFieldLayout( title = title, @@ -45,7 +45,7 @@ fun POLabeledDropdownField( preferFormattedTextSelection = preferFormattedTextSelection, enabled = enabled, isError = isError, - placeholderText = placeholderText + placeholder = placeholder ) } } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POLabeledPhoneNumberField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POLabeledPhoneNumberField.kt deleted file mode 100644 index 8c35c954f..000000000 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POLabeledPhoneNumberField.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.processout.sdk.ui.core.component.field.phone - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.input.TextFieldValue -import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi -import com.processout.sdk.ui.core.component.field.LabeledFieldLayout -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.dropdown.PODropdownField -import com.processout.sdk.ui.core.state.POPhoneNumberFieldState - -/** @suppress */ -@ProcessOutInternalApi -@Composable -fun POLabeledPhoneNumberField( - state: POPhoneNumberFieldState, - onValueChange: (TextFieldValue, TextFieldValue) -> Unit, - modifier: Modifier = Modifier, - textFieldModifier: Modifier = Modifier, - fieldStyle: POField.Style = POField.default, - dropdownMenuStyle: PODropdownField.MenuStyle = PODropdownField.defaultMenu, - labelsStyle: POFieldLabels.Style = POFieldLabels.default, - keyboardActions: KeyboardActions = KeyboardActions.Default -) { - LabeledFieldLayout( - title = state.title ?: String(), - description = state.description, - style = labelsStyle - ) { - POPhoneNumberField( - state = state, - onValueChange = onValueChange, - modifier = modifier, - textFieldModifier = textFieldModifier, - fieldStyle = fieldStyle, - dropdownMenuStyle = dropdownMenuStyle, - keyboardActions = keyboardActions - ) - } -} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POPhoneNumberField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POPhoneNumberField.kt index d59c87930..aa689b230 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POPhoneNumberField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/phone/POPhoneNumberField.kt @@ -2,19 +2,22 @@ package com.processout.sdk.ui.core.component.field.phone import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.dp import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POMessageBox +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.dropdown.PODropdownField -import com.processout.sdk.ui.core.component.field.text.POTextField +import com.processout.sdk.ui.core.component.field.dropdown.PODropdownField2 +import com.processout.sdk.ui.core.component.field.text.POTextField2 import com.processout.sdk.ui.core.state.POPhoneNumberFieldState import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing @@ -26,71 +29,87 @@ fun POPhoneNumberField( onValueChange: (TextFieldValue, TextFieldValue) -> Unit, modifier: Modifier = Modifier, textFieldModifier: Modifier = Modifier, - fieldStyle: POField.Style = POField.default, - dropdownMenuStyle: PODropdownField.MenuStyle = PODropdownField.defaultMenu, + fieldStyle: POField.Style = POField.default2, + dropdownMenuStyle: PODropdownField.MenuStyle = PODropdownField.defaultMenu2, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, keyboardActions: KeyboardActions = KeyboardActions.Default ) { - Row(modifier = modifier) { - PODropdownField( - value = state.regionCode, - onValueChange = { regionCode -> - onValueChange(regionCode, state.number) - }, - availableValues = state.regionCodes, - modifier = Modifier.width(IntrinsicSize.Min), - fieldContentPadding = PaddingValues( - start = spacing.large, - end = 0.dp, - top = spacing.medium, - bottom = spacing.medium - ), - fieldStyle = fieldStyle, - menuStyle = dropdownMenuStyle, - menuMatchesTextFieldWidth = false, - preferFormattedTextSelection = true, - isError = state.isError, - placeholderText = state.regionCodePlaceholder - ) - val phoneNumberUtil = remember { PhoneNumberUtil.getInstance() } - POTextField( - value = state.number, - onValueChange = { number -> - if (number.text.startsWith('+')) { - try { - val filteredNumber = number.text.filterIndexed { index, char -> - (index == 0 && char == '+') || char.isDigit() - } - val parsedNumber = phoneNumberUtil.parse(filteredNumber, null) - val parsedRegionCode = phoneNumberUtil.getRegionCodeForCountryCode(parsedNumber.countryCode) - var regionCode = state.regionCode - if (state.regionCodes.elements.any { it.value == parsedRegionCode }) { - regionCode = TextFieldValue(text = parsedRegionCode) + Column(modifier = modifier) { + Row(modifier = Modifier.fillMaxWidth()) { + var requestFocus by remember { mutableStateOf(false) } + PODropdownField2( + value = state.regionCode, + onValueChange = { regionCode -> + requestFocus = true + onValueChange(regionCode, state.number) + }, + availableValues = state.regionCodes, + modifier = Modifier.width(IntrinsicSize.Min), + contentPadding = PaddingValues( + start = spacing.space12, + end = spacing.space0, + top = spacing.space6, + bottom = spacing.space6 + ), + fieldStyle = fieldStyle, + menuStyle = dropdownMenuStyle, + menuMatchesTextFieldWidth = false, + preferFormattedTextSelection = true, + isError = state.isError, + label = state.regionCodeLabel + ) + val focusRequester = remember { FocusRequester() } + val phoneNumberUtil = remember { PhoneNumberUtil.getInstance() } + POTextField2( + value = state.number, + onValueChange = { number -> + if (number.text.startsWith('+')) { + try { + val filteredNumber = number.text.filterIndexed { index, char -> + (index == 0 && char == '+') || char.isDigit() + } + val parsedNumber = phoneNumberUtil.parse(filteredNumber, null) + val parsedRegionCode = phoneNumberUtil.getRegionCodeForCountryCode(parsedNumber.countryCode) + var regionCode = state.regionCode + if (state.regionCodes.elements.any { it.value == parsedRegionCode }) { + regionCode = TextFieldValue(text = parsedRegionCode) + } + val parsedNationalNumber = parsedNumber.nationalNumber.toString() + val nationalNumber = TextFieldValue( + text = parsedNationalNumber, + selection = TextRange(parsedNationalNumber.length) + ) + onValueChange(regionCode, nationalNumber) + } catch (e: NumberParseException) { + // ignore } - val parsedNationalNumber = parsedNumber.nationalNumber.toString() - val nationalNumber = TextFieldValue( - text = parsedNationalNumber, - selection = TextRange(parsedNationalNumber.length) - ) - onValueChange(regionCode, nationalNumber) - } catch (e: NumberParseException) { - // ignore + } else { + val filteredNumber = state.inputFilter?.filter(number) ?: number + onValueChange(state.regionCode, filteredNumber) } - } else { - val filteredNumber = state.inputFilter?.filter(number) ?: number - onValueChange(state.regionCode, filteredNumber) - } - }, - modifier = textFieldModifier - .padding(start = spacing.extraSmall) - .weight(1f), - style = fieldStyle, - enabled = state.enabled, - isError = state.isError, - forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.numberPlaceholder, - visualTransformation = state.visualTransformation ?: VisualTransformation.None, - keyboardOptions = state.keyboardOptions, - keyboardActions = keyboardActions + }, + modifier = Modifier + .padding(start = spacing.space4) + .weight(1f), + textFieldModifier = textFieldModifier.focusRequester(focusRequester), + fieldStyle = fieldStyle, + enabled = state.enabled, + isError = state.isError, + forceTextDirectionLtr = state.forceTextDirectionLtr, + label = state.numberLabel, + visualTransformation = state.visualTransformation ?: VisualTransformation.None, + keyboardOptions = state.keyboardOptions, + keyboardActions = keyboardActions + ) + if (requestFocus) { + requestFocus = false + PORequestFocus(focusRequester) + } + } + POMessageBox( + text = state.description, + modifier = Modifier.padding(top = spacing.space12), + style = descriptionStyle ) } } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioButton.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioButton.kt index 527328ba7..76f81e3e4 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioButton.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioButton.kt @@ -10,12 +10,13 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi -import com.processout.sdk.ui.core.component.field.radio.PORadioButton.RadioButtonScale -import com.processout.sdk.ui.core.component.field.radio.PORadioButton.RadioButtonSize +import com.processout.sdk.ui.core.component.field.radio.PORadioButton.MaterialRadioButtonSize import com.processout.sdk.ui.core.component.field.radio.PORadioButton.colors -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 /** @suppress */ @ProcessOutInternalApi @@ -24,17 +25,19 @@ fun PORadioButton( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, + radioButtonSize: Dp = MaterialRadioButtonSize, style: PORadioButton.Style = PORadioButton.default, enabled: Boolean = true, isError: Boolean = false ) { + val radioButtonScale = radioButtonSize.value / MaterialRadioButtonSize.value RadioButton( selected = selected, onClick = onClick, modifier = modifier - .scale(RadioButtonScale) - .requiredWidth(RadioButtonSize) - .requiredHeight(ProcessOutTheme.dimensions.formComponentMinHeight), + .scale(radioButtonScale) + .requiredWidth(radioButtonSize) + .requiredHeight(dimensions.formComponentMinHeight), enabled = enabled, colors = colors(style = style, isError = isError) ) @@ -53,18 +56,22 @@ object PORadioButton { ) val default: Style - @Composable get() = with(ProcessOutTheme) { - Style( - normalColor = colors.input.borderDefault, - selectedColor = colors.button.primaryBackgroundDefault, - errorColor = colors.input.borderError, - disabledColor = colors.input.borderDisabled - ) - } + @Composable get() = Style( + normalColor = colors.input.borderDefault, + selectedColor = colors.button.primaryBackgroundDefault, + errorColor = colors.input.borderError, + disabledColor = colors.input.borderDisabled + ) + + val default2: Style + @Composable get() = Style( + normalColor = colors.checkRadio.borderDefault, + selectedColor = colors.checkRadio.borderActive, + errorColor = colors.checkRadio.borderError, + disabledColor = colors.checkRadio.iconDisabled + ) - private val MaterialRadioButtonSize = 20.dp - internal val RadioButtonSize = 22.dp - internal val RadioButtonScale = RadioButtonSize.value / MaterialRadioButtonSize.value + internal val MaterialRadioButtonSize = 20.dp @Composable internal fun colors( 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 new file mode 100644 index 000000000..ecac0ddfe --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioField.kt @@ -0,0 +1,265 @@ +package com.processout.sdk.ui.core.component.field.radio + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +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.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POBorderStroke +import com.processout.sdk.ui.core.component.POMessageBox +import com.processout.sdk.ui.core.component.POText +import com.processout.sdk.ui.core.component.POText.measuredPaddingTop +import com.processout.sdk.ui.core.component.field.radio.PORadioField.optionStateStyle +import com.processout.sdk.ui.core.component.field.radio.PORadioField.radioButtonStyle +import com.processout.sdk.ui.core.component.field.radio.PORadioField.stateStyle +import com.processout.sdk.ui.core.state.POAvailableValue +import com.processout.sdk.ui.core.state.POImmutableList +import com.processout.sdk.ui.core.style.PORadioFieldStateStyle +import com.processout.sdk.ui.core.style.PORadioFieldStyle +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 + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun PORadioField( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + availableValues: POImmutableList, + modifier: Modifier = Modifier, + fieldStyle: PORadioField.Style = PORadioField.default, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, + title: String? = null, + description: String? = null, + isError: Boolean = false +) { + Column(modifier = modifier) { + val stateStyle = stateStyle( + style = fieldStyle, + isSelected = value.text.isNotBlank(), + isError = isError + ) + if (!title.isNullOrBlank()) { + POText( + text = title, + modifier = Modifier.padding(bottom = spacing.space12), + color = stateStyle.title.color, + style = stateStyle.title.textStyle + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .border( + width = stateStyle.border.width, + color = stateStyle.border.color, + shape = stateStyle.shape + ) + .padding(spacing.space4), + verticalArrangement = Arrangement.spacedBy(spacing.space2) + ) { + availableValues.elements.forEach { availableValue -> + val isSelected = availableValue.value == value.text + val optionStateStyle = optionStateStyle( + style = fieldStyle, + isSelected = isSelected, + isError = isError + ) + val onClick = { onValueChange(TextFieldValue(text = availableValue.value)) } + val interactionSource = remember { MutableInteractionSource() } + val rowMinHeight = dimensions.formComponentMinHeight + Row( + modifier = Modifier + .fillMaxWidth() + .requiredHeightIn(min = rowMinHeight) + .clip(shape = shapes.roundedCorners4) + .clickable( + onClick = onClick, + interactionSource = interactionSource, + indication = optionStateStyle.rowRippleColor?.let { ripple(color = it) } + ) + .background(optionStateStyle.rowBackgroundColor) + .padding(start = spacing.space12) + ) { + PORadioButton( + selected = isSelected, + onClick = onClick, + radioButtonSize = 16.dp, + style = fieldStyle.radioButtonStyle(), + isError = isError + ) + POText( + text = availableValue.text, + modifier = Modifier.padding( + start = spacing.space10, + top = measuredPaddingTop( + textStyle = optionStateStyle.option.textStyle, + componentHeight = rowMinHeight + ), + bottom = spacing.space10 + ), + color = optionStateStyle.option.color, + style = optionStateStyle.option.textStyle + ) + } + } + } + POMessageBox( + text = description, + modifier = Modifier.padding(top = spacing.space12), + style = descriptionStyle + ) + } +} + +/** @suppress */ +@ProcessOutInternalApi +object PORadioField { + + @Immutable + data class Style( + val normal: StateStyle, + val selected: StateStyle, + val error: StateStyle, + val disabled: StateStyle + ) + + @Immutable + data class StateStyle( + val title: POText.Style, + val option: POText.Style, + val radioButtonColor: Color, + val rowBackgroundColor: Color, + val rowRippleColor: Color?, + val shape: Shape, + val border: POBorderStroke + ) + + val default: Style + @Composable get() = Style( + normal = StateStyle( + title = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ), + option = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + radioButtonColor = PORadioButton.default2.normalColor, + rowBackgroundColor = colors.surface.default, + rowRippleColor = colors.surface.darkoutRipple, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderDefault2) + ), + selected = StateStyle( + title = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ), + option = POText.Style( + color = colors.text.primary, + textStyle = typography.s15(FontWeight.Medium) + ), + radioButtonColor = PORadioButton.default2.selectedColor, + rowBackgroundColor = colors.surface.darkout, + rowRippleColor = null, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderDefault2) + ), + error = StateStyle( + title = POText.Style( + color = colors.text.error, + textStyle = typography.s16(FontWeight.Medium) + ), + option = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ), + radioButtonColor = PORadioButton.default2.errorColor, + rowBackgroundColor = colors.surface.default, + rowRippleColor = colors.surface.darkoutRipple, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderError) + ), + disabled = StateStyle( + title = POText.Style( + color = colors.text.primary, + textStyle = typography.s16(FontWeight.Medium) + ), + option = POText.Style( + color = colors.text.disabled, + textStyle = typography.s15(FontWeight.Medium) + ), + radioButtonColor = PORadioButton.default2.disabledColor, + rowBackgroundColor = colors.surface.default, + rowRippleColor = null, + shape = shapes.roundedCorners6, + border = POBorderStroke(width = 1.5.dp, color = colors.input.borderDefault2) + ) + ) + + @Composable + fun custom(style: PORadioFieldStyle) = Style( + normal = style.normal.toStateStyle(), + selected = style.selected.toStateStyle(), + error = style.error.toStateStyle(), + disabled = style.disabled?.toStateStyle() ?: default.disabled + ) + + @Composable + private fun PORadioFieldStateStyle.toStateStyle() = StateStyle( + title = POText.custom(style = title), + option = POText.custom(style = option), + radioButtonColor = colorResource(id = radioButtonColorResId), + rowBackgroundColor = colorResource(id = rowBackgroundColorResId), + rowRippleColor = rowRippleColorResId?.let { colorResource(id = it) }, + shape = RoundedCornerShape(size = border.radiusDp.dp), + border = POBorderStroke( + width = border.widthDp.dp, + color = colorResource(id = border.colorResId) + ) + ) + + internal fun stateStyle( + style: Style, + isSelected: Boolean, + isError: Boolean + ): StateStyle = + if (isError) style.error + else if (isSelected) style.selected + else style.normal + + internal fun optionStateStyle( + style: Style, + isSelected: Boolean, + isError: Boolean + ): StateStyle = + if (isSelected) style.selected + else if (isError) style.error + else style.normal + + internal fun Style.radioButtonStyle() = PORadioButton.Style( + normalColor = normal.radioButtonColor, + selectedColor = selected.radioButtonColor, + errorColor = error.radioButtonColor, + disabledColor = disabled.radioButtonColor + ) +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioGroup.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioGroup.kt index e27aa6a77..9986579dd 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioGroup.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/radio/PORadioGroup.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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.component.POText @@ -19,6 +20,8 @@ import com.processout.sdk.ui.core.state.POAvailableValue import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.style.PORadioButtonStateStyle import com.processout.sdk.ui.core.style.PORadioButtonStyle +import com.processout.sdk.ui.core.style.PORadioFieldStateStyle +import com.processout.sdk.ui.core.style.PORadioFieldStyle 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.typography @@ -116,16 +119,45 @@ object PORadioGroup { ) ) - @Composable - fun custom(style: PORadioButtonStyle): Style { - val normal = style.normal.toStateStyle() - return Style( - normal = normal, - selected = style.selected.toStateStyle(), - error = style.error.toStateStyle(), - disabled = style.disabled?.toStateStyle() ?: normal + val default2: Style + @Composable get() = Style( + normal = StateStyle( + buttonColor = PORadioButton.default2.normalColor, + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ) + ), + selected = StateStyle( + buttonColor = PORadioButton.default2.selectedColor, + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ) + ), + error = StateStyle( + buttonColor = PORadioButton.default2.errorColor, + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.s15(FontWeight.Medium) + ) + ), + disabled = StateStyle( + buttonColor = PORadioButton.default2.disabledColor, + text = POText.Style( + color = colors.text.disabled, + textStyle = typography.s15(FontWeight.Medium) + ) + ) ) - } + + @Composable + fun custom(style: PORadioButtonStyle) = Style( + normal = style.normal.toStateStyle(), + selected = style.selected.toStateStyle(), + error = style.error.toStateStyle(), + disabled = style.disabled?.toStateStyle() ?: default.disabled + ) @Composable private fun PORadioButtonStateStyle.toStateStyle() = StateStyle( @@ -133,6 +165,20 @@ object PORadioGroup { text = POText.custom(style = text) ) + @Composable + fun custom(style: PORadioFieldStyle) = Style( + normal = style.normal.toStateStyle(), + selected = style.selected.toStateStyle(), + error = style.error.toStateStyle(), + disabled = style.disabled?.toStateStyle() ?: default.disabled + ) + + @Composable + private fun PORadioFieldStateStyle.toStateStyle() = StateStyle( + buttonColor = colorResource(id = radioButtonColorResId), + text = POText.custom(style = option) + ) + fun Style.toRadioButtonStyle() = PORadioButton.Style( normalColor = normal.buttonColor, selectedColor = selected.buttonColor, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POLabeledTextField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POLabeledTextField.kt index 95cfab7f5..268e7b214 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POLabeledTextField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POLabeledTextField.kt @@ -36,7 +36,7 @@ fun POLabeledTextField( isDropdown: Boolean = false, isError: Boolean = false, forceTextDirectionLtr: Boolean = false, - placeholderText: String? = null, + placeholder: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, visualTransformation: VisualTransformation = VisualTransformation.None, @@ -63,7 +63,7 @@ fun POLabeledTextField( isDropdown = isDropdown, isError = isError, forceTextDirectionLtr = forceTextDirectionLtr, - placeholderText = placeholderText, + placeholder = placeholder, leadingIcon = leadingIcon, trailingIcon = trailingIcon, visualTransformation = visualTransformation, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField.kt index 7f37a7b78..65ac7eca6 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField.kt @@ -2,6 +2,10 @@ package com.processout.sdk.ui.core.component.field.text +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField @@ -16,8 +20,11 @@ import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi import com.processout.sdk.ui.core.component.POText import com.processout.sdk.ui.core.component.field.POField @@ -25,7 +32,8 @@ import com.processout.sdk.ui.core.component.field.POField.ContainerBox import com.processout.sdk.ui.core.component.field.POField.stateStyle import com.processout.sdk.ui.core.component.field.POField.textSelectionColors import com.processout.sdk.ui.core.component.field.POField.textStyle -import com.processout.sdk.ui.core.theme.ProcessOutTheme +import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing /** @suppress */ @ProcessOutInternalApi @@ -34,6 +42,7 @@ fun POTextField( value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, modifier: Modifier = Modifier, + minHeight: Dp = dimensions.formComponentMinHeight, contentPadding: PaddingValues = POField.contentPadding, style: POField.Style = POField.default, enabled: Boolean = true, @@ -41,7 +50,8 @@ fun POTextField( isDropdown: Boolean = false, isError: Boolean = false, forceTextDirectionLtr: Boolean = false, - placeholderText: String? = null, + label: String? = null, + placeholder: String? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, visualTransformation: VisualTransformation = VisualTransformation.None, @@ -61,7 +71,7 @@ fun POTextField( value = value, onValueChange = onValueChange, modifier = modifier - .requiredHeight(ProcessOutTheme.dimensions.formComponentMinHeight) + .requiredHeightIn(min = minHeight) .onFocusChanged { isFocused = it.isFocused }, @@ -82,18 +92,49 @@ fun POTextField( decorationBox = @Composable { innerTextField -> OutlinedTextFieldDefaults.DecorationBox( value = value.text, - innerTextField = innerTextField, - enabled = enabled, - isError = isError, - placeholder = { - if (!placeholderText.isNullOrBlank()) { - POText( - text = placeholderText, - color = stateStyle.placeholderTextColor, - style = stateStyle.text.textStyle + innerTextField = { + val animationStiffness = Spring.StiffnessMedium + Column( + modifier = Modifier.animateContentSize( + animationSpec = spring(stiffness = animationStiffness) ) + ) { + val isLabelFloating = value.text.isNotEmpty() || (isFocused && !isDropdown) + if (!label.isNullOrBlank()) { + val fontSizeValue = stateStyle.text.textStyle.fontSize.value + val animatedFontSizeValue by animateFloatAsState( + targetValue = if (isLabelFloating) fontSizeValue * 0.8f else fontSizeValue, + animationSpec = spring(stiffness = animationStiffness) + ) + POText( + text = label, + color = stateStyle.label.color, + style = stateStyle.label.textStyle.copy(fontSize = animatedFontSizeValue.sp), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + if (isLabelFloating) { + Spacer(modifier = Modifier.requiredHeight(spacing.space2)) + } + } + if (isLabelFloating || label.isNullOrBlank()) { + Box { + if (value.text.isEmpty() && !placeholder.isNullOrBlank()) { + POText( + text = placeholder, + color = stateStyle.placeholderTextColor, + style = stateStyle.text.textStyle, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + innerTextField() + } + } } }, + enabled = enabled, + isError = isError, leadingIcon = leadingIcon, trailingIcon = trailingIcon, singleLine = singleLine, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField2.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField2.kt new file mode 100644 index 000000000..1fb1332dd --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/field/text/POTextField2.kt @@ -0,0 +1,83 @@ +package com.processout.sdk.ui.core.component.field.text + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.Dp +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.component.POMessageBox +import com.processout.sdk.ui.core.component.field.POField +import com.processout.sdk.ui.core.theme.ProcessOutTheme.dimensions +import com.processout.sdk.ui.core.theme.ProcessOutTheme.spacing + +/** @suppress */ +@ProcessOutInternalApi +@Composable +fun POTextField2( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier = Modifier, + textFieldModifier: Modifier = Modifier, + minHeight: Dp = dimensions.fieldMinHeight, + contentPadding: PaddingValues = POField.contentPadding2, + fieldStyle: POField.Style = POField.default2, + descriptionStyle: POMessageBox.Style = POMessageBox.error2, + enabled: Boolean = true, + readOnly: Boolean = false, + isDropdown: Boolean = false, + isError: Boolean = false, + forceTextDirectionLtr: Boolean = false, + label: String? = null, + placeholder: String? = null, + description: String? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = true, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } +) { + Column(modifier = modifier) { + POTextField( + value = value, + onValueChange = onValueChange, + modifier = textFieldModifier.fillMaxWidth(), + minHeight = minHeight, + contentPadding = contentPadding, + style = fieldStyle, + enabled = enabled, + readOnly = readOnly, + isDropdown = isDropdown, + isError = isError, + forceTextDirectionLtr = forceTextDirectionLtr, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + interactionSource = interactionSource + ) + POMessageBox( + text = description, + modifier = Modifier.padding(top = spacing.space12), + style = descriptionStyle + ) + } +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/state/POPhoneNumberFieldState.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/state/POPhoneNumberFieldState.kt index 623a2db96..7a1ac1beb 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/state/POPhoneNumberFieldState.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/state/POPhoneNumberFieldState.kt @@ -13,10 +13,9 @@ data class POPhoneNumberFieldState( val id: String, val regionCode: TextFieldValue, val regionCodes: POImmutableList, - val regionCodePlaceholder: String?, + val regionCodeLabel: String, val number: TextFieldValue, - val numberPlaceholder: String?, - val title: String? = null, + val numberLabel: String, val description: String? = null, val enabled: Boolean = true, val isError: Boolean = false, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POCheckboxStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POCheckboxStyle.kt index ceea75dfa..19d3d8e8e 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POCheckboxStyle.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POCheckboxStyle.kt @@ -15,7 +15,9 @@ data class POCheckboxStyle( @Parcelize data class POCheckboxStateStyle( val checkmark: POCheckmarkStyle, - val text: POTextStyle + val text: POTextStyle, + @ColorRes + val rippleColorResId: Int? = null ) : Parcelable @Parcelize diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POFieldStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POFieldStyle.kt index e334c3d54..51852a6f8 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POFieldStyle.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POFieldStyle.kt @@ -14,6 +14,7 @@ data class POFieldStyle( @Parcelize data class POFieldStateStyle( val text: POTextStyle, + val label: POTextStyle, @ColorRes val placeholderTextColorResId: Int, @ColorRes @@ -23,4 +24,33 @@ data class POFieldStateStyle( val border: POBorderStyle, @ColorRes val dropdownRippleColorResId: Int? = null -) : Parcelable +) : Parcelable { + + @Deprecated(message = "Use alternative constructor.") + constructor( + text: POTextStyle, + @ColorRes + placeholderTextColorResId: Int, + @ColorRes + backgroundColorResId: Int, + @ColorRes + controlsTintColorResId: Int, + border: POBorderStyle, + @ColorRes + dropdownRippleColorResId: Int? = null + ) : this( + text = text, + label = POTextStyle( + colorResId = 0, + type = POTextType( + textSizeSp = 0, + lineHeightSp = 0 + ) + ), + placeholderTextColorResId = placeholderTextColorResId, + backgroundColorResId = backgroundColorResId, + controlsTintColorResId = controlsTintColorResId, + border = border, + dropdownRippleColorResId = dropdownRippleColorResId + ) +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/PORadioFieldStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/PORadioFieldStyle.kt new file mode 100644 index 000000000..85c36be18 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/PORadioFieldStyle.kt @@ -0,0 +1,26 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PORadioFieldStyle( + val normal: PORadioFieldStateStyle, + val selected: PORadioFieldStateStyle, + val error: PORadioFieldStateStyle, + val disabled: PORadioFieldStateStyle? = null +) : Parcelable + +@Parcelize +data class PORadioFieldStateStyle( + val title: POTextStyle, + val option: POTextStyle, + @ColorRes + val radioButtonColorResId: Int, + @ColorRes + val rowBackgroundColorResId: Int, + @ColorRes + val rowRippleColorResId: Int?, + val border: POBorderStyle +) : 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 62357e77d..53a166706 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 @@ -12,6 +12,7 @@ import com.processout.sdk.ui.core.theme.POColors.* data class POColors( val text: Text, val input: Input, + val checkRadio: CheckRadio, val button: Button, val surface: Surface, val border: Border @@ -19,11 +20,15 @@ data class POColors( @Immutable data class Text( val primary: Color, + val secondary: Color, val inverse: Color, val muted: Color, + val placeholder: Color, val disabled: Color, + val onButtonDisabled: Color, val success: Color, - val error: Color + val error: Color, + val onTipError: Color ) @Immutable @@ -31,11 +36,28 @@ data class POColors( val backgroundDefault: Color, val backgroundDisabled: Color, val borderDefault: Color, + val borderDefault2: Color, val borderDisabled: Color, val borderFocused: Color, val borderError: Color ) + @Immutable + data class CheckRadio( + val iconDefault: Color, + val iconActive: Color, + val iconError: Color, + val iconDisabled: Color, + val borderDefault: Color, + val borderActive: Color, + val borderError: Color, + val borderDisabled: Color, + val surfaceDefault: Color, + val surfaceActive: Color, + val surfaceError: Color, + val surfaceDisabled: Color + ) + @Immutable data class Button( val primaryBackgroundDefault: Color, @@ -44,9 +66,7 @@ data class POColors( val secondaryBackgroundDefault: Color, val secondaryBackgroundDisabled: Color, val secondaryBackgroundPressed: Color, - val secondaryBorderDefault: Color, - val secondaryBorderDisabled: Color, - val secondaryBorderPressed: Color, + val ghostBackgroundDisabled: Color, val ghostBackgroundPressed: Color ) @@ -54,8 +74,11 @@ data class POColors( data class Surface( val default: Color, val neutral: Color, + val darkout: Color, + val darkoutRipple: Color, val success: Color, - val error: Color + val error: Color, + val toastError: Color ) @Immutable @@ -67,38 +90,58 @@ data class POColors( @ProcessOutInternalApi val POLightColorPalette = POColors( text = Text( - primary = Color(0xFF121821), - inverse = Color(0xFFFAFAFA), + primary = Color(0xFF000000), + secondary = Color(0xFF585A5F), + inverse = Color(0xFFFFFFFF), muted = Color(0xFF5B6576), + placeholder = Color(0xFF707378), disabled = Color(0xFFADB5BD), + onButtonDisabled = Color(0xFFC0C3C8), success = Color(0xFF00291D), - error = Color(0xFFD11D2F) + error = Color(0xFFBE011B), + onTipError = Color(0xFF630407) ), input = Input( backgroundDefault = Color(0xFFFFFFFF), - backgroundDisabled = Color(0xFFEDEEEF), + backgroundDisabled = Color(0x0F121314), borderDefault = Color(0xFF7C8593), + borderDefault2 = Color(0x1F121314), borderDisabled = Color(0xFFADB5BD), borderFocused = Color(0xFF4791FF), - borderError = Color(0xFFD11D2F) + borderError = Color(0xFFBE011B) + ), + checkRadio = CheckRadio( + iconDefault = Color(0xFFFFFFFF), + iconActive = Color(0xFFFFFFFF), + iconError = Color(0xFFF03030), + iconDisabled = Color(0xFFC0C3C8), + borderDefault = Color(0xFFC0C3C8), + borderActive = Color(0xFF000000), + borderError = Color(0xFFF03030), + borderDisabled = Color(0xFFE2E2E2), + surfaceDefault = Color(0xFFFFFFFF), + surfaceActive = Color(0xFF000000), + surfaceError = Color(0xFFFDE3DE), + surfaceDisabled = Color(0xFFF1F1F2) ), button = Button( - primaryBackgroundDefault = Color(0xFF121821), - primaryBackgroundDisabled = Color(0xFFEDEEEF), - primaryBackgroundPressed = Color(0xFF242C38), - secondaryBackgroundDefault = Color(0xFFFFFFFF), - secondaryBackgroundDisabled = Color(0xFFFFFFFF), + primaryBackgroundDefault = Color(0xFF000000), + primaryBackgroundDisabled = Color(0x0A121314), + primaryBackgroundPressed = Color(0xFF26292F), + secondaryBackgroundDefault = Color(0x0F121314), + secondaryBackgroundDisabled = Color(0x0A121314), secondaryBackgroundPressed = Color(0x29212222), - secondaryBorderDefault = Color(0xFF121821), - secondaryBorderDisabled = Color(0xFFEDEEEF), - secondaryBorderPressed = Color(0xFF242C38), + ghostBackgroundDisabled = Color(0x0A121314), ghostBackgroundPressed = Color(0x1F121314) ), surface = Surface( default = Color(0xFFFFFFFF), neutral = Color(0xFFFAFAFA), + darkout = Color(0x0F121314), + darkoutRipple = Color(0x0F59595A), success = Color(0xFFBEFAE9), - error = Color(0xFFFFC2C8) + error = Color(0xFFFFC2C8), + toastError = Color(0xFFFDE3DE) ), border = Border( subtle = Color(0xFFCCD1D6) @@ -108,38 +151,58 @@ val POLightColorPalette = POColors( @ProcessOutInternalApi val PODarkColorPalette = POColors( text = Text( - primary = Color(0xFFFAFAFA), - inverse = Color(0xFF121821), + primary = Color(0xFFFFFFFF), + secondary = Color(0xFFC0C3C8), + inverse = Color(0xFF000000), muted = Color(0xFFADB5BD), + placeholder = Color(0xFFA7A9AF), disabled = Color(0xFF5B6576), + onButtonDisabled = Color(0xFF707378), success = Color(0xFFE5FFF8), - error = Color(0xFFFF5263) + error = Color(0xFFFF7D6C), + onTipError = Color(0xFFF5D9D9) ), input = Input( - backgroundDefault = Color(0xFF121821), - backgroundDisabled = Color(0xFF242C38), + backgroundDefault = Color(0xFF26292F), + backgroundDisabled = Color(0x14F6F8FB), borderDefault = Color(0xFFCCD1D6), + borderDefault2 = Color(0x29F6F8FB), borderDisabled = Color(0xFF7C8593), borderFocused = Color(0xFFFFE500), - borderError = Color(0xFFFF5263) + borderError = Color(0xFFFF7D6C) + ), + checkRadio = CheckRadio( + iconDefault = Color(0x33121314), + iconActive = Color(0xFF000000), + iconError = Color(0xFFFF7D6C), + iconDisabled = Color(0xFF707378), + borderDefault = Color(0x3DF6F8FB), + borderActive = Color(0xFFFFFFFF), + borderError = Color(0xFFFF7D6C), + borderDisabled = Color(0xFF3D4149), + surfaceDefault = Color(0x33121314), + surfaceActive = Color(0xFFFFFFFF), + surfaceError = Color(0xFF3D0D04), + surfaceDisabled = Color(0xFF2E3137) ), button = Button( primaryBackgroundDefault = Color(0xFFFFFFFF), - primaryBackgroundDisabled = Color(0xFF242C38), - primaryBackgroundPressed = Color(0xFFCCD1D6), - secondaryBackgroundDefault = Color(0xFF121821), - secondaryBackgroundDisabled = Color(0xFF121821), + primaryBackgroundDisabled = Color(0xFF2E3137), + primaryBackgroundPressed = Color(0xFF585A5F), + secondaryBackgroundDefault = Color(0x14F6F8FB), + secondaryBackgroundDisabled = Color(0xFF2E3137), secondaryBackgroundPressed = Color(0x0FF6F8FB), - secondaryBorderDefault = Color(0xFFFFFFFF), - secondaryBorderDisabled = Color(0xFF242C38), - secondaryBorderPressed = Color(0xFFCCD1D6), + ghostBackgroundDisabled = Color(0xFF2E3137), ghostBackgroundPressed = Color(0x1FF6F8FB) ), surface = Surface( - default = Color(0xFF121821), - neutral = Color(0xFF242C38), + default = Color(0xFF26292F), + neutral = Color(0xFF2A2D34), + darkout = Color(0x0FF6F8FB), + darkoutRipple = Color(0x0FACADAF), success = Color(0xFF1DA37D), - error = Color(0xFFD11D2F) + error = Color(0xFFD11D2F), + toastError = Color(0xFF511511) ), border = Border( subtle = Color(0xFF7C8593) diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Dimensions.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Dimensions.kt index 5ad12b4e3..5bee82a3b 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Dimensions.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Dimensions.kt @@ -10,6 +10,7 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @ProcessOutInternalApi @Immutable data class PODimensions( + val fieldMinHeight: Dp = 52.dp, val formComponentMinHeight: Dp = 48.dp, val interactiveComponentMinSize: Dp = 44.dp, val iconSizeSmall: Dp = 16.dp, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Shapes.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Shapes.kt index c725e7306..c98a6335a 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Shapes.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Shapes.kt @@ -11,6 +11,9 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @ProcessOutInternalApi @Immutable data class POShapes( + val roundedCorners4: CornerBasedShape = RoundedCornerShape(4.dp), + val roundedCorners6: CornerBasedShape = RoundedCornerShape(6.dp), + val roundedCorners8: CornerBasedShape = RoundedCornerShape(8.dp), val roundedCornersSmall: CornerBasedShape = RoundedCornerShape(4.dp), val roundedCornersMedium: CornerBasedShape = RoundedCornerShape(8.dp), val roundedCornersLarge: CornerBasedShape = RoundedCornerShape(16.dp), diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Spacing.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Spacing.kt index dfbc48bf0..cebf2061f 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Spacing.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Spacing.kt @@ -10,6 +10,15 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @ProcessOutInternalApi @Immutable data class POSpacing( + val space0: Dp = 0.dp, + val space2: Dp = 2.dp, + val space4: Dp = 4.dp, + val space6: Dp = 6.dp, + val space8: Dp = 8.dp, + val space10: Dp = 10.dp, + val space12: Dp = 12.dp, + val space16: Dp = 16.dp, + val space20: Dp = 20.dp, val extraSmall: Dp = 4.dp, val small: Dp = 8.dp, val medium: Dp = 12.dp, diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Typography.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Typography.kt index 11fe578aa..b8eaab73b 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Typography.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Typography.kt @@ -15,13 +15,15 @@ import com.processout.sdk.ui.core.style.POTextType.Weight.* private val WorkSans = FontFamily( Font(R.font.work_sans_regular, FontWeight.Normal), - Font(R.font.work_sans_medium, FontWeight.Medium) + Font(R.font.work_sans_medium, FontWeight.Medium), + Font(R.font.work_sans_semibold, FontWeight.SemiBold) ) /** @suppress */ @ProcessOutInternalApi @Immutable data class POTypography( + val paragraph: Paragraph = Paragraph, val title: TextStyle = TextStyle( fontFamily = WorkSans, fontWeight = FontWeight.Medium, @@ -76,7 +78,95 @@ data class POTypography( fontSize = 14.sp, lineHeight = 18.sp ) -) +) { + + fun s12(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 12.sp, + lineHeight = 14.sp, + letterSpacing = 0.15.sp + ) + + fun s13(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 13.sp, + lineHeight = 16.sp + ) + + fun s14(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.15.sp + ) + + fun s15(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 15.sp, + lineHeight = 18.sp + ) + + fun s16(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 16.sp, + lineHeight = 20.sp + ) + + fun s18(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 18.sp, + lineHeight = 22.sp, + letterSpacing = when (fontWeight) { + FontWeight.Medium, + FontWeight.SemiBold -> 0.1.sp + else -> 0.sp + } + ) + + fun s20(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 20.sp, + lineHeight = 24.sp, + letterSpacing = when (fontWeight) { + FontWeight.Medium -> (-0.15).sp + FontWeight.SemiBold -> (-0.1).sp + else -> (-0.2).sp + } + ) + + fun s24(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 24.sp, + lineHeight = 28.sp + ) + + object Paragraph { + + fun s16(fontWeight: FontWeight = FontWeight.Normal) = + TextStyle( + fontFamily = WorkSans, + fontWeight = fontWeight, + fontSize = 16.sp, + lineHeight = 26.sp + ) + } +} internal val LocalPOTypography = staticCompositionLocalOf { POTypography() } diff --git a/ui-core/src/main/res/drawable/po_chevron_down.xml b/ui-core/src/main/res/drawable/po_chevron_down.xml new file mode 100644 index 000000000..9dbbb88f3 --- /dev/null +++ b/ui-core/src/main/res/drawable/po_chevron_down.xml @@ -0,0 +1,13 @@ + + + diff --git a/ui-core/src/main/res/drawable/po_info_icon.xml b/ui-core/src/main/res/drawable/po_icon_info.xml similarity index 100% rename from ui-core/src/main/res/drawable/po_info_icon.xml rename to ui-core/src/main/res/drawable/po_icon_info.xml diff --git a/ui-core/src/main/res/drawable/po_icon_warning_diamond.xml b/ui-core/src/main/res/drawable/po_icon_warning_diamond.xml new file mode 100644 index 000000000..661700537 --- /dev/null +++ b/ui-core/src/main/res/drawable/po_icon_warning_diamond.xml @@ -0,0 +1,13 @@ + + + diff --git a/ui-core/src/main/res/font/work_sans_medium.ttf b/ui-core/src/main/res/font/work_sans_medium.ttf index 1800fe2d8..216807382 100644 Binary files a/ui-core/src/main/res/font/work_sans_medium.ttf and b/ui-core/src/main/res/font/work_sans_medium.ttf differ diff --git a/ui-core/src/main/res/font/work_sans_regular.ttf b/ui-core/src/main/res/font/work_sans_regular.ttf index 20c724037..d24586cc0 100644 Binary files a/ui-core/src/main/res/font/work_sans_regular.ttf and b/ui-core/src/main/res/font/work_sans_regular.ttf differ diff --git a/ui-core/src/main/res/font/work_sans_semibold.ttf b/ui-core/src/main/res/font/work_sans_semibold.ttf new file mode 100644 index 000000000..a75721ccf Binary files /dev/null and b/ui-core/src/main/res/font/work_sans_semibold.ttf differ diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationScreen.kt index 0878f5da7..6bd090e2b 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationScreen.kt @@ -300,7 +300,7 @@ private fun TextField( enabled = state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, + placeholder = state.placeholder, trailingIcon = { state.iconResId?.let { AnimatedFieldIcon(id = it) } }, visualTransformation = state.visualTransformation, keyboardOptions = state.keyboardOptions, @@ -373,7 +373,7 @@ private fun DropdownField( menuStyle = menuStyle, enabled = state.enabled, isError = state.isError, - placeholderText = state.placeholder + placeholder = state.placeholder ) } @@ -385,7 +385,7 @@ private fun CheckboxField( modifier: Modifier = Modifier ) { POCheckbox( - text = state.title ?: String(), + text = state.label ?: String(), checked = state.value.text.toBooleanStrictOrNull() ?: false, onCheckedChange = { if (state.enabled) { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt index 29f5cc753..3faee7f0d 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt @@ -458,7 +458,7 @@ internal class CardTokenizationViewModel private constructor( FieldState( id = field.id, value = field.value, - title = title, + label = title, enabled = field.enabled, isError = !field.isValid ) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/update/CardUpdateScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/update/CardUpdateScreen.kt index 99510d703..8a83be8e2 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/update/CardUpdateScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/update/CardUpdateScreen.kt @@ -149,7 +149,7 @@ private fun Fields( readOnly = !state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, + placeholder = state.placeholder, trailingIcon = { state.iconResId?.let { AnimatedFieldIcon(id = it) } }, keyboardOptions = state.keyboardOptions, keyboardActions = POField.keyboardActions( 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 91ecab1de..c60ba2cdb 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 @@ -374,7 +374,7 @@ internal class DynamicCheckoutViewModel private constructor( FieldState( id = id, value = value, - title = title + label = title ) ) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/CardTokenization.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/CardTokenization.kt index 9905056b0..012a50d07 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/CardTokenization.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/CardTokenization.kt @@ -245,7 +245,7 @@ private fun TextField( enabled = state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, + placeholder = state.placeholder, trailingIcon = { state.iconResId?.let { AnimatedFieldIcon(id = it) } }, visualTransformation = state.visualTransformation, keyboardOptions = state.keyboardOptions, @@ -330,7 +330,7 @@ private fun DropdownField( menuStyle = menuStyle, enabled = state.enabled, isError = state.isError, - placeholderText = state.placeholder + placeholder = state.placeholder ) } @@ -343,7 +343,7 @@ private fun CheckboxField( modifier: Modifier = Modifier ) { POCheckbox( - text = state.title ?: String(), + text = state.label ?: String(), checked = state.value.text.toBooleanStrictOrNull() ?: false, onCheckedChange = { if (state.enabled) { 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/screen/DynamicCheckoutScreen.kt index 5dc582707..7e0fdf078 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/DynamicCheckoutScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/screen/DynamicCheckoutScreen.kt @@ -546,7 +546,7 @@ private fun CheckboxField( modifier: Modifier = Modifier ) { POCheckbox( - text = state.title ?: String(), + text = state.label ?: String(), checked = state.value.text.toBooleanStrictOrNull() ?: false, onCheckedChange = { onEvent( @@ -790,7 +790,7 @@ internal object DynamicCheckoutScreen { title = POText.body1, description = POTextWithIcon.Style( text = description, - iconResId = R.drawable.po_info_icon, + iconResId = R.drawable.po_icon_info, iconColorFilter = ColorFilter.tint(color = description.color) ), shape = shapes.roundedCornersSmall, 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 index 76384b3bf..495797012 100644 --- 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 @@ -149,7 +149,7 @@ private fun TextField( ) ) }, - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .focusRequester(focusRequester) @@ -167,7 +167,7 @@ private fun TextField( enabled = state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, + placeholder = state.placeholder, visualTransformation = state.visualTransformation, keyboardOptions = state.keyboardOptions, keyboardActions = POField.keyboardActions( @@ -213,7 +213,7 @@ private fun CodeField( ) ) }, - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .onFocusChanged { @@ -271,7 +271,7 @@ private fun RadioField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier, radioGroupStyle = radioGroupStyle, @@ -302,7 +302,7 @@ private fun DropdownField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .onFocusChanged { @@ -318,7 +318,7 @@ private fun DropdownField( labelsStyle = labelsStyle, menuStyle = menuStyle, isError = state.isError, - placeholderText = state.placeholder + placeholder = state.placeholder ) } 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 9b2360de8..532ccec9a 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 @@ -229,7 +229,7 @@ private fun TextField( ) ) }, - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .focusRequester(focusRequester) @@ -246,7 +246,7 @@ private fun TextField( enabled = state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, + placeholder = state.placeholder, visualTransformation = state.visualTransformation, keyboardOptions = state.keyboardOptions, keyboardActions = POField.keyboardActions( @@ -283,7 +283,7 @@ private fun CodeField( ) ) }, - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .onFocusChanged { @@ -331,7 +331,7 @@ private fun RadioField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier, radioGroupStyle = radioGroupStyle, @@ -360,7 +360,7 @@ private fun DropdownField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), + title = state.label ?: String(), description = state.description, modifier = modifier .onFocusChanged { @@ -375,7 +375,7 @@ private fun DropdownField( labelsStyle = labelsStyle, menuStyle = menuStyle, isError = state.isError, - placeholderText = state.placeholder + placeholder = state.placeholder ) } @@ -643,7 +643,7 @@ internal object NativeAlternativePaymentScreen { codeField = custom?.codeField?.let { POField.custom(style = it) } ?: POCodeField.default, - radioGroup = custom?.radioButton?.let { + radioGroup = custom?.radioField?.let { PORadioGroup.custom(style = it) } ?: PORadioGroup.default, dropdownMenu = custom?.dropdownMenu?.let { 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 217dd9c4c..208105149 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 @@ -231,7 +231,7 @@ internal class NativeAlternativePaymentViewModel private constructor( FieldState( id = id, value = value, - title = displayName, + label = displayName, description = description, placeholder = type.placeholder(), isError = !isValid, @@ -255,7 +255,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value, length = length, - title = displayName, + label = displayName, description = description, isError = !isValid, keyboardOptions = type.keyboardOptions(keyboardAction.imeAction), @@ -269,7 +269,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value, availableValues = availableValues?.let { POImmutableList(it) }, - title = displayName, + label = displayName, description = description, isError = !isValid ) @@ -281,7 +281,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value, availableValues = availableValues?.let { POImmutableList(it) }, - title = displayName, + label = displayName, description = description, isError = !isValid ) @@ -336,8 +336,8 @@ internal class NativeAlternativePaymentViewModel private constructor( } private fun ParameterType.placeholder(): String? = when (this) { - EMAIL -> app.getString(R.string.po_native_apm_email_placeholder) - PHONE -> app.getString(R.string.po_native_apm_phone_placeholder) + EMAIL -> app.getString(R.string.po_native_apm_placeholder_email) + PHONE -> app.getString(R.string.po_native_apm_placeholder_phone) else -> null } 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 eadceb8ff..ad4195774 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 @@ -294,7 +294,7 @@ data class PONativeAlternativePaymentConfiguration( * @param[label] Field label style. * @param[field] Field style. * @param[codeField] Code field style. - * @param[radioButton] Radio button style. + * @param[radioField] Radio field style. * @param[checkbox] Checkbox style. * @param[dropdownMenu] Dropdown menu style. * @param[actionsContainer] Style of action buttons and their container. @@ -302,6 +302,7 @@ data class PONativeAlternativePaymentConfiguration( * @param[background] Background style. * @param[message] Message style. * @param[errorMessage] Error message style. + * @param[errorMessageBox] Error message box style. * @param[successMessage] Success message style. * @param[successImageResId] Success image drawable resource ID. * @param[progressIndicatorColorResId] Color resource ID for progress indicator. @@ -315,7 +316,7 @@ data class PONativeAlternativePaymentConfiguration( val label: POTextStyle? = null, val field: POFieldStyle? = null, val codeField: POFieldStyle? = null, - val radioButton: PORadioButtonStyle? = null, + val radioField: PORadioFieldStyle? = null, val checkbox: POCheckboxStyle? = null, val dropdownMenu: PODropdownMenuStyle? = null, val actionsContainer: POActionsContainerStyle? = null, @@ -323,6 +324,7 @@ data class PONativeAlternativePaymentConfiguration( val background: POBackgroundStyle? = null, val message: POTextStyle? = null, val errorMessage: POTextStyle? = null, + val errorMessageBox: POMessageBoxStyle? = null, val successMessage: POTextStyle? = null, @DrawableRes val successImageResId: Int? = null, @@ -334,7 +336,79 @@ data class PONativeAlternativePaymentConfiguration( val dividerColorResId: Int? = null, @ColorRes val dragHandleColorResId: Int? = null - ) : Parcelable + ) : Parcelable { + + /** + * Allows to customize the look and feel. + * + * @param[title] Title style. + * @param[label] Field label style. + * @param[field] Field style. + * @param[codeField] Code field style. + * @param[radioButton] Radio button style. __Deprecated__: not used. + * @param[checkbox] Checkbox style. + * @param[dropdownMenu] Dropdown menu style. + * @param[actionsContainer] Style of action buttons and their container. + * @param[dialog] Dialog style. + * @param[background] Background style. + * @param[message] Message style. + * @param[errorMessage] Error message style. + * @param[errorMessageBox] Error message box style. + * @param[successMessage] Success message style. + * @param[successImageResId] Success image drawable resource ID. + * @param[progressIndicatorColorResId] Color resource ID for progress indicator. + * @param[controlsTintColorResId] Color resource ID for tint that applies to generic components (e.g. selectable text). + * @param[dividerColorResId] Color resource ID for title divider. + * @param[dragHandleColorResId] Color resource ID for bottom sheet drag handle. + */ + @Deprecated(message = "Use alternative constructor.") + constructor( + title: POTextStyle? = null, + label: POTextStyle? = null, + field: POFieldStyle? = null, + codeField: POFieldStyle? = null, + radioButton: PORadioButtonStyle? = null, + checkbox: POCheckboxStyle? = null, + dropdownMenu: PODropdownMenuStyle? = null, + actionsContainer: POActionsContainerStyle? = null, + dialog: PODialogStyle? = null, + background: POBackgroundStyle? = null, + message: POTextStyle? = null, + errorMessage: POTextStyle? = null, + errorMessageBox: POMessageBoxStyle? = null, + successMessage: POTextStyle? = null, + @DrawableRes + successImageResId: Int? = null, + @ColorRes + progressIndicatorColorResId: Int? = null, + @ColorRes + controlsTintColorResId: Int? = null, + @ColorRes + dividerColorResId: Int? = null, + @ColorRes + dragHandleColorResId: Int? = null + ) : this( + title = title, + label = label, + field = field, + codeField = codeField, + radioField = null, + checkbox = checkbox, + dropdownMenu = dropdownMenu, + actionsContainer = actionsContainer, + dialog = dialog, + background = background, + message = message, + errorMessage = errorMessage, + errorMessageBox = errorMessageBox, + successMessage = successMessage, + successImageResId = successImageResId, + progressIndicatorColorResId = progressIndicatorColorResId, + controlsTintColorResId = controlsTintColorResId, + dividerColorResId = dividerColorResId, + dragHandleColorResId = dragHandleColorResId + ) + } } private fun SecondaryAction.toCancelButton(): CancelButton = diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt index 182d8e8ba..39078855f 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt @@ -290,10 +290,9 @@ internal class NativeAlternativePaymentInteractor( return } val fields = parameters.toFields() - val focusedFieldId = fields.firstFocusableFieldId() val updatedStateValue = stateValue.copy( fields = fields, - focusedFieldId = focusedFieldId + focusedFieldId = fields.firstFocusableFieldId() ) _state.update { if (_state.value is Loading) { @@ -327,7 +326,7 @@ internal class NativeAlternativePaymentInteractor( map { parameter -> val defaultValue = when (parameter) { is Parameter.SingleSelect -> FieldValue.Text( - TextFieldValue(text = parameter.preselectedValue?.value ?: String()) + TextFieldValue(text = parameter.preselectedValue?.key ?: String()) ) is Parameter.Bool -> FieldValue.Text(TextFieldValue(text = "false")) is Parameter.PhoneNumber -> FieldValue.PhoneNumber() diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentScreen.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentScreen.kt index 5ebaf7bb7..5df874a64 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentScreen.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentScreen.kt @@ -36,17 +36,15 @@ import coil.compose.AsyncImage import com.processout.sdk.ui.R import com.processout.sdk.ui.core.component.* 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.checkbox.POCheckbox -import com.processout.sdk.ui.core.component.field.checkbox.POLabeledCheckboxField +import com.processout.sdk.ui.core.component.field.checkbox.POCheckboxField 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.code.POCodeField2 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.phone.POLabeledPhoneNumberField -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.component.field.dropdown.PODropdownField2 +import com.processout.sdk.ui.core.component.field.phone.POPhoneNumberField +import com.processout.sdk.ui.core.component.field.radio.PORadioField +import com.processout.sdk.ui.core.component.field.text.POTextField2 import com.processout.sdk.ui.core.state.POActionState import com.processout.sdk.ui.core.state.POImmutableList import com.processout.sdk.ui.core.state.POPhoneNumberFieldState @@ -60,7 +58,6 @@ import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.CaptureImage import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.CaptureLogoHeight import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.CrossfadeAnimationDurationMillis import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.animatedBackgroundColor -import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.codeFieldHorizontalAlignment import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentScreen.messageGravity import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentViewModelState.* import com.processout.sdk.ui.napm.v2.NativeAlternativePaymentViewModelState.Field.* @@ -165,10 +162,6 @@ private fun UserInput( verticalArrangement = Arrangement.spacedBy(ProcessOutTheme.spacing.extraLarge) ) { val lifecycleEvent = rememberLifecycleEvent() - val labelsStyle = POFieldLabels.Style( - title = style.label, - description = style.errorMessage - ) val isPrimaryActionEnabled = with(state.primaryAction) { enabled && !loading } state.fields.elements.forEach { field -> when (field) { @@ -179,7 +172,7 @@ private fun UserInput( focusedFieldId = state.focusedFieldId, isPrimaryActionEnabled = isPrimaryActionEnabled, fieldStyle = style.field, - labelsStyle = labelsStyle, + descriptionStyle = style.errorMessageBox, modifier = Modifier.fillMaxWidth() ) is CodeField -> CodeField( @@ -189,28 +182,29 @@ private fun UserInput( focusedFieldId = state.focusedFieldId, isPrimaryActionEnabled = isPrimaryActionEnabled, fieldStyle = style.codeField, - labelsStyle = labelsStyle, - horizontalAlignment = codeFieldHorizontalAlignment(state.fields.elements) + descriptionStyle = style.errorMessageBox, + modifier = Modifier.fillMaxWidth() ) is RadioField -> RadioField( state = field.state, onEvent = onEvent, - radioGroupStyle = style.radioGroup, - labelsStyle = labelsStyle + fieldStyle = style.radioField, + descriptionStyle = style.errorMessageBox, + modifier = Modifier.fillMaxWidth() ) is DropdownField -> DropdownField( state = field.state, onEvent = onEvent, fieldStyle = style.field, - labelsStyle = labelsStyle, menuStyle = style.dropdownMenu, + descriptionStyle = style.errorMessageBox, modifier = Modifier.fillMaxWidth() ) is CheckboxField -> CheckboxField( state = field.state, onEvent = onEvent, checkboxStyle = style.checkbox, - labelsStyle = labelsStyle, + descriptionStyle = style.errorMessageBox, modifier = Modifier.fillMaxWidth() ) is PhoneNumberField -> PhoneNumberField( @@ -221,7 +215,7 @@ private fun UserInput( isPrimaryActionEnabled = isPrimaryActionEnabled, fieldStyle = style.field, dropdownMenuStyle = style.dropdownMenu, - labelsStyle = labelsStyle, + descriptionStyle = style.errorMessageBox, modifier = Modifier.fillMaxWidth() ) } @@ -238,11 +232,11 @@ private fun TextField( focusedFieldId: String?, isPrimaryActionEnabled: Boolean, fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { val focusRequester = remember { FocusRequester() } - POLabeledTextField( + POTextField2( value = state.value, onValueChange = { onEvent( @@ -252,9 +246,8 @@ private fun TextField( ) ) }, - title = state.title ?: String(), - description = state.description, - modifier = modifier + modifier = modifier, + textFieldModifier = Modifier .focusRequester(focusRequester) .onFocusChanged { onEvent( @@ -265,11 +258,13 @@ private fun TextField( ) }, fieldStyle = fieldStyle, - labelsStyle = labelsStyle, + descriptionStyle = descriptionStyle, + label = state.label, + placeholder = state.placeholder, + description = state.description, enabled = state.enabled, isError = state.isError, forceTextDirectionLtr = state.forceTextDirectionLtr, - placeholderText = state.placeholder, visualTransformation = state.visualTransformation, keyboardOptions = state.keyboardOptions, keyboardActions = POField.keyboardActions( @@ -292,11 +287,10 @@ private fun CodeField( focusedFieldId: String?, isPrimaryActionEnabled: Boolean, fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, - horizontalAlignment: Alignment.Horizontal, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { - POLabeledCodeField( + POCodeField2( value = state.value, onValueChange = { onEvent( @@ -306,9 +300,8 @@ private fun CodeField( ) ) }, - title = state.title ?: String(), - description = state.description, - modifier = modifier + modifier = modifier, + textFieldModifier = Modifier .onFocusChanged { onEvent( FieldFocusChanged( @@ -318,9 +311,10 @@ private fun CodeField( ) }, fieldStyle = fieldStyle, - labelsStyle = labelsStyle, + descriptionStyle = descriptionStyle, length = state.length ?: POCodeField.LengthMax, - horizontalAlignment = horizontalAlignment, + label = state.label, + description = state.description, enabled = state.enabled, isError = state.isError, isFocused = state.id == focusedFieldId, @@ -340,11 +334,11 @@ private fun CodeField( private fun RadioField( state: FieldState, onEvent: (NativeAlternativePaymentEvent) -> Unit, - radioGroupStyle: PORadioGroup.Style, - labelsStyle: POFieldLabels.Style, + fieldStyle: PORadioField.Style, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { - POLabeledRadioField( + PORadioField( value = state.value, onValueChange = { onEvent( @@ -355,11 +349,11 @@ private fun RadioField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), - description = state.description, modifier = modifier, - radioGroupStyle = radioGroupStyle, - labelsStyle = labelsStyle, + fieldStyle = fieldStyle, + descriptionStyle = descriptionStyle, + title = state.label, + description = state.description, isError = state.isError ) } @@ -369,11 +363,11 @@ private fun DropdownField( state: FieldState, onEvent: (NativeAlternativePaymentEvent) -> Unit, fieldStyle: POField.Style, - labelsStyle: POFieldLabels.Style, menuStyle: PODropdownField.MenuStyle, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { - POLabeledDropdownField( + PODropdownField2( value = state.value, onValueChange = { onEvent( @@ -384,9 +378,8 @@ private fun DropdownField( ) }, availableValues = state.availableValues ?: POImmutableList(emptyList()), - title = state.title ?: String(), - description = state.description, - modifier = modifier + modifier = modifier, + textFieldModifier = Modifier .onFocusChanged { onEvent( FieldFocusChanged( @@ -396,10 +389,12 @@ private fun DropdownField( ) }, fieldStyle = fieldStyle, - labelsStyle = labelsStyle, menuStyle = menuStyle, + descriptionStyle = descriptionStyle, isError = state.isError, - placeholderText = state.placeholder + label = state.label, + placeholder = state.placeholder, + description = state.description ) } @@ -408,11 +403,11 @@ private fun CheckboxField( state: FieldState, onEvent: (NativeAlternativePaymentEvent) -> Unit, checkboxStyle: POCheckbox.Style, - labelsStyle: POFieldLabels.Style, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { - POLabeledCheckboxField( - text = state.title ?: String(), + POCheckboxField( + text = state.label ?: String(), checked = state.value.text.toBooleanStrictOrNull() ?: false, onCheckedChange = { onEvent( @@ -424,12 +419,11 @@ private fun CheckboxField( ) ) }, - title = null, - description = state.description, modifier = modifier, checkboxStyle = checkboxStyle, - labelsStyle = labelsStyle, - isError = state.isError + descriptionStyle = descriptionStyle, + isError = state.isError, + description = state.description ) } @@ -442,11 +436,11 @@ private fun PhoneNumberField( isPrimaryActionEnabled: Boolean, fieldStyle: POField.Style, dropdownMenuStyle: PODropdownField.MenuStyle, - labelsStyle: POFieldLabels.Style, + descriptionStyle: POMessageBox.Style, modifier: Modifier = Modifier ) { val focusRequester = remember { FocusRequester() } - POLabeledPhoneNumberField( + POPhoneNumberField( state = state, onValueChange = { regionCode, number -> onEvent( @@ -472,7 +466,7 @@ private fun PhoneNumberField( }, fieldStyle = fieldStyle, dropdownMenuStyle = dropdownMenuStyle, - labelsStyle = labelsStyle, + descriptionStyle = descriptionStyle, keyboardActions = POField.keyboardActions( imeAction = state.keyboardOptions.imeAction, actionId = state.keyboardActionId, @@ -718,7 +712,7 @@ internal object NativeAlternativePaymentScreen { val label: POText.Style, val field: POField.Style, val codeField: POField.Style, - val radioGroup: PORadioGroup.Style, + val radioField: PORadioField.Style, val checkbox: POCheckbox.Style, val dropdownMenu: PODropdownField.MenuStyle, val actionsContainer: POActionsContainer.Style, @@ -727,6 +721,7 @@ internal object NativeAlternativePaymentScreen { val successBackgroundColor: Color, val message: AndroidTextView.Style, val errorMessage: POText.Style, + val errorMessageBox: POMessageBox.Style, val successMessage: POText.Style, @DrawableRes val successImageResId: Int, val progressIndicatorColor: Color, @@ -746,22 +741,22 @@ internal object NativeAlternativePaymentScreen { } ?: POText.label1, field = custom?.field?.let { POField.custom(style = it) - } ?: POField.default, + } ?: POField.default2, codeField = custom?.codeField?.let { POField.custom(style = it) - } ?: POCodeField.default, - radioGroup = custom?.radioButton?.let { - PORadioGroup.custom(style = it) - } ?: PORadioGroup.default, + } ?: POCodeField.default2, + radioField = custom?.radioField?.let { + PORadioField.custom(style = it) + } ?: PORadioField.default, checkbox = custom?.checkbox?.let { POCheckbox.custom(style = it) - } ?: POCheckbox.default, + } ?: POCheckbox.default2, dropdownMenu = custom?.dropdownMenu?.let { PODropdownField.custom(style = it) - } ?: PODropdownField.defaultMenu, + } ?: PODropdownField.defaultMenu2, actionsContainer = custom?.actionsContainer?.let { POActionsContainer.custom(style = it) - } ?: POActionsContainer.default, + } ?: POActionsContainer.default2, dialog = custom?.dialog?.let { PODialog.custom(style = it) } ?: PODialog.default, @@ -781,6 +776,9 @@ internal object NativeAlternativePaymentScreen { errorMessage = custom?.errorMessage?.let { POText.custom(style = it) } ?: POText.errorLabel, + errorMessageBox = custom?.errorMessageBox?.let { + POMessageBox.custom(style = it) + } ?: POMessageBox.error2, successMessage = custom?.successMessage?.let { POText.custom(style = it) } ?: POText.Style( @@ -824,10 +822,6 @@ internal object NativeAlternativePaymentScreen { ) ).value - fun codeFieldHorizontalAlignment(fields: List): Alignment.Horizontal = - if (fields.size == 1 && fields[0] is CodeField) - Alignment.CenterHorizontally else Alignment.Start - private val ShortMessageMaxLength = 150 fun messageGravity(text: String): Int = diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentViewModel.kt index 4c0bd514e..5b852d98a 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentViewModel.kt @@ -250,9 +250,8 @@ internal class NativeAlternativePaymentViewModel private constructor( FieldState( id = id, value = value.textFieldValue(), - title = label, + label = label, description = description, - placeholder = parameter.placeholder(), isError = !isValid, forceTextDirectionLtr = ltrParameterTypes.contains(parameter::class.java), inputFilter = parameter.inputFilter(), @@ -270,7 +269,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value.textFieldValue(), length = maxLength, - title = label, + label = label, description = description, isError = !isValid, inputFilter = parameter.inputFilter(), @@ -285,7 +284,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value.textFieldValue(), availableValues = parameter.availableValues(), - title = label, + label = label, description = description, isError = !isValid ) @@ -297,7 +296,7 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, value = value.textFieldValue(), availableValues = parameter.availableValues(), - title = label, + label = label, description = description, isError = !isValid ) @@ -308,7 +307,7 @@ internal class NativeAlternativePaymentViewModel private constructor( FieldState( id = id, value = value.textFieldValue(), - title = label, + label = label, description = description, isError = !isValid ) @@ -326,13 +325,12 @@ internal class NativeAlternativePaymentViewModel private constructor( id = id, regionCode = regionCode, regionCodes = parameter.phoneNumberRegionCodes(), - regionCodePlaceholder = app.getString(R.string.po_native_apm_country_placeholder), + regionCodeLabel = app.getString(R.string.po_native_apm_label_country), number = when (value) { is FieldValue.PhoneNumber -> value.number else -> TextFieldValue() }, - numberPlaceholder = app.getString(R.string.po_native_apm_phone_placeholder), - title = label, + numberLabel = label, description = description, isError = !isValid, forceTextDirectionLtr = true, @@ -358,7 +356,7 @@ internal class NativeAlternativePaymentViewModel private constructor( is SingleSelect -> POImmutableList( availableValues.map { POAvailableValue( - value = it.value, + value = it.key, text = it.label ) } @@ -460,12 +458,6 @@ internal class NativeAlternativePaymentViewModel private constructor( Unknown -> KeyboardOptions.Default } - private fun Parameter.placeholder(): String? = - when (this) { - is Email -> app.getString(R.string.po_native_apm_email_placeholder) - else -> null - } - private fun Invoice.formatPrimaryActionText() = try { val price = NumberFormat.getCurrencyInstance().apply { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/shared/state/FieldState.kt b/ui/src/main/kotlin/com/processout/sdk/ui/shared/state/FieldState.kt index 3c4bb10f6..f99e648cf 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/shared/state/FieldState.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/shared/state/FieldState.kt @@ -15,9 +15,9 @@ internal data class FieldState( val value: TextFieldValue = TextFieldValue(), val availableValues: POImmutableList? = null, val length: Int? = null, - val title: String? = null, - val description: String? = null, + val label: String? = null, val placeholder: String? = null, + val description: String? = null, @DrawableRes val iconResId: Int? = null, val enabled: Boolean = true, val isError: Boolean = false,