From bfaf6ee313e73c7eed61a80da6a4b66b564612e8 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak <114916876+vitalii-vanziak-cko@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:44:38 +0300 Subject: [PATCH] feat(POM-274): Composable button (#128) --- build.gradle | 8 +- ui-core/build.gradle | 1 + .../sdk/ui/core/component/POBorderStroke.kt | 20 ++ .../sdk/ui/core/component/POButton.kt | 267 ++++++++++++++++++ .../component/POCircularProgressIndicator.kt | 28 ++ .../sdk/ui/core/component/POText.kt | 68 +++++ .../sdk/ui/core/style/POBackgroundStyle.kt | 13 + .../sdk/ui/core/style/POBorderStyle.kt | 13 + .../sdk/ui/core/style/POTextStyle.kt | 12 + .../processout/sdk/ui/core/style/POType.kt | 43 +++ .../ui/core/style/button/POButtonDefaults.kt | 6 + .../style/button/POButtonHighlightedStyle.kt | 15 + .../core/style/button/POButtonStateStyle.kt | 18 ++ .../sdk/ui/core/style/button/POButtonStyle.kt | 14 + .../style/dropdown/ExposedDropdownStyle.kt | 9 + .../ui/core/style/input/POInputFieldStyle.kt | 19 ++ .../ui/core/style/input/POInputStateStyle.kt | 12 + .../sdk/ui/core/style/input/POInputStyle.kt | 10 + .../style/radio/PORadioButtonStateStyle.kt | 13 + .../ui/core/style/radio/PORadioButtonStyle.kt | 17 ++ .../processout/sdk/ui/core/theme/Colors.kt | 188 +++++++----- .../sdk/ui/core/theme/Dimensions.kt | 3 +- .../theme/{Theme.kt => ProcessOutTheme.kt} | 37 +-- .../sdk/ui/core/theme/Typography.kt | 57 ++-- 24 files changed, 772 insertions(+), 119 deletions(-) create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POBorderStroke.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCircularProgressIndicator.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POText.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBackgroundStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBorderStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POTextStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POType.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonDefaults.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonHighlightedStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStateStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/dropdown/ExposedDropdownStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputFieldStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStateStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStateStyle.kt create mode 100644 ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStyle.kt rename ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/{Theme.kt => ProcessOutTheme.kt} (59%) diff --git a/build.gradle b/build.gradle index 578fca95..2242bc84 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,11 @@ buildscript { ext { - androidGradlePluginVersion = '8.1.1' + androidGradlePluginVersion = '8.1.2' kotlinVersion = '1.9.10' kspVersion = '1.9.10-1.0.13' dokkaVersion = '1.9.0' - androidxNavigationVersion = '2.7.3' + androidxNavigationVersion = '2.7.4' nexusPublishPluginVersion = '1.3.0' } dependencies { @@ -38,14 +38,14 @@ ext { androidxCoreVersion = '1.12.0' androidxAppCompatVersion = '1.6.1' androidxConstraintLayoutVersion = '2.1.4' - androidxActivityVersion = '1.7.2' + androidxActivityVersion = '1.8.0' androidxFragmentVersion = '1.6.1' androidxLifecycleVersion = '2.6.2' androidxRecyclerViewVersion = '1.3.1' androidxSwipeRefreshLayoutVersion = '1.1.0' androidxBrowserVersion = '1.6.0' - androidxComposeBOMVersion = '2023.09.02' + androidxComposeBOMVersion = '2023.10.00' androidxComposeCompilerVersion = '1.5.3' materialVersion = '1.9.0' diff --git a/ui-core/build.gradle b/ui-core/build.gradle index e26b7ca1..d345f39d 100644 --- a/ui-core/build.gradle +++ b/ui-core/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' + id 'kotlin-parcelize' } android { diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POBorderStroke.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POBorderStroke.kt new file mode 100644 index 00000000..4d5fa096 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POBorderStroke.kt @@ -0,0 +1,20 @@ +package com.processout.sdk.ui.core.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi + +/** @suppress */ +@ProcessOutInternalApi +@Immutable +data class POBorderStroke( + val width: Dp, + val color: Color +) + +internal fun POBorderStroke.solid( + width: Dp = this.width, + color: Color = this.color +) = BorderStroke(width = width, color = color) 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 new file mode 100644 index 00000000..15dd1637 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POButton.kt @@ -0,0 +1,267 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.processout.sdk.ui.core.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ButtonElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.colorResource +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 com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.style.button.POButtonDefaults +import com.processout.sdk.ui.core.style.button.POButtonStateStyle +import com.processout.sdk.ui.core.style.button.POButtonStyle +import com.processout.sdk.ui.core.theme.ProcessOutTheme + +/** @suppress */ +@ProcessOutInternalApi +object POButton { + + @Composable + operator fun invoke( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: Style = primary, + enabled: Boolean = true, + loading: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + ) { + val pressed by interactionSource.collectIsPressedAsState() + Button( + onClick = onClick, + modifier = modifier.defaultMinSize(minHeight = ProcessOutTheme.dimensions.formComponentHeight), + enabled = enabled && !loading, + colors = colors(enabled = enabled, loading = loading, pressed = pressed, style = style), + shape = if (enabled) style.normal.shape else style.disabled.shape, + border = border(enabled = enabled, pressed = pressed, style = style), + elevation = elevation(enabled = enabled, loading = loading, style = style), + contentPadding = contentPadding(enabled = enabled, style = style), + interactionSource = interactionSource + ) { + if (enabled && loading) { + POCircularProgressIndicator.Small(color = style.progressIndicatorColor) + } else { + POText( + text = text, + style = if (enabled) style.normal.text.textStyle else style.disabled.text.textStyle, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + } + } + + @Composable + private fun colors( + enabled: Boolean, + loading: Boolean, + pressed: Boolean, + style: Style + ): ButtonColors { + val normalTextColor: Color + val normalBackgroundColor: Color + if (pressed) with(style.highlighted) { + normalTextColor = textColor + normalBackgroundColor = backgroundColor + } else with(style.normal) { + normalTextColor = text.color + normalBackgroundColor = backgroundColor + } + + val disabledTextColor: Color + val disabledBackgroundColor: Color + if (enabled && loading) with(style.normal) { + disabledTextColor = text.color + disabledBackgroundColor = backgroundColor + } else with(style.disabled) { + disabledTextColor = text.color + disabledBackgroundColor = backgroundColor + } + + return ButtonDefaults.buttonColors( + containerColor = normalBackgroundColor, + contentColor = normalTextColor, + disabledContainerColor = disabledBackgroundColor, + disabledContentColor = disabledTextColor + ) + } + + private fun border( + enabled: Boolean, + pressed: Boolean, + style: Style + ): BorderStroke { + val normalBorderColor = if (pressed) style.highlighted.borderColor else style.normal.border.color + return if (enabled) style.normal.border.solid(color = normalBorderColor) + else style.disabled.border.solid() + } + + @Composable + private fun elevation( + enabled: Boolean, + loading: Boolean, + style: Style + ): ButtonElevation = ButtonDefaults.buttonElevation( + defaultElevation = style.normal.elevation, + pressedElevation = style.normal.elevation, + focusedElevation = style.normal.elevation, + hoveredElevation = style.normal.elevation, + disabledElevation = if (enabled && loading) + style.normal.elevation else style.disabled.elevation + ) + + private fun contentPadding( + enabled: Boolean, + style: Style + ): PaddingValues = if (enabled) PaddingValues( + horizontal = style.normal.paddingHorizontal, + vertical = style.normal.paddingVertical + ) else PaddingValues( + horizontal = style.disabled.paddingHorizontal, + vertical = style.disabled.paddingVertical + ) + + @Immutable + data class Style( + val normal: StateStyle, + val disabled: StateStyle, + val highlighted: HighlightedStyle, + val progressIndicatorColor: Color + ) + + @Immutable + data class StateStyle( + val text: POText.Style, + val shape: Shape, + val border: POBorderStroke, + val backgroundColor: Color, + val elevation: Dp, + val paddingHorizontal: Dp = POButtonDefaults.PADDING_HORIZONTAL_DP.dp, + val paddingVertical: Dp = POButtonDefaults.PADDING_VERTICAL_DP.dp + ) + + @Immutable + data class HighlightedStyle( + val textColor: Color, + val borderColor: Color, + val backgroundColor: Color + ) + + val primary: Style + @Composable get() = with(ProcessOutTheme) { + Style( + normal = StateStyle( + text = POText.Style( + color = colors.text.onColor, + textStyle = typography.fixed.button + ), + shape = shapes.roundedCornersSmall, + border = POBorderStroke(width = 0.dp, color = Color.Unspecified), + backgroundColor = colors.action.primaryDefault, + elevation = 2.dp + ), + disabled = StateStyle( + text = POText.Style( + color = colors.text.disabled, + textStyle = typography.fixed.button + ), + shape = shapes.roundedCornersSmall, + border = POBorderStroke(width = 0.dp, color = Color.Unspecified), + backgroundColor = colors.action.primaryDisabled, + elevation = 0.dp + ), + highlighted = HighlightedStyle( + textColor = colors.text.onColor, + borderColor = Color.Unspecified, + backgroundColor = colors.action.primaryPressed + ), + progressIndicatorColor = colors.text.onColor + ) + } + + val secondary: Style + @Composable get() = with(ProcessOutTheme) { + Style( + normal = StateStyle( + text = POText.Style( + color = colors.text.secondary, + textStyle = typography.fixed.button + ), + shape = shapes.roundedCornersSmall, + border = POBorderStroke(width = 1.dp, color = colors.border.default), + backgroundColor = colors.action.secondaryDefault, + elevation = 0.dp + ), + disabled = StateStyle( + text = POText.Style( + color = colors.text.disabled, + textStyle = typography.fixed.button + ), + shape = shapes.roundedCornersSmall, + border = POBorderStroke(width = 1.dp, color = colors.action.borderDisabled), + backgroundColor = colors.action.secondaryDefault, + elevation = 0.dp + ), + highlighted = HighlightedStyle( + textColor = colors.text.secondary, + borderColor = colors.border.default, + backgroundColor = colors.action.secondaryPressed + ), + progressIndicatorColor = colors.text.secondary + ) + } + + @Composable + fun custom(style: POButtonStyle) = Style( + normal = style.normal.toStateStyle(), + disabled = style.disabled.toStateStyle(), + highlighted = with(style.highlighted) { + HighlightedStyle( + textColor = colorResource(id = textColorResId), + borderColor = colorResource(id = borderColorResId), + backgroundColor = colorResource(id = backgroundColorResId) + ) + }, + progressIndicatorColor = colorResource(id = style.progressIndicatorColorResId) + ) + + @Composable + private fun POButtonStateStyle.toStateStyle() = StateStyle( + text = POText.custom(style = text), + shape = RoundedCornerShape(size = border.radiusDp.dp), + border = POBorderStroke(width = border.widthDp.dp, color = colorResource(id = border.colorResId)), + backgroundColor = colorResource(id = backgroundColorResId), + elevation = elevationDp.dp, + paddingHorizontal = paddingHorizontalDp.dp, + paddingVertical = paddingVerticalDp.dp + ) +} + +@Preview(showBackground = true) +@Composable +internal fun POButtonPreview() { + POButton( + text = "Button", + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCircularProgressIndicator.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCircularProgressIndicator.kt new file mode 100644 index 00000000..7a00b795 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POCircularProgressIndicator.kt @@ -0,0 +1,28 @@ +package com.processout.sdk.ui.core.component + +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi + +/** @suppress */ +@ProcessOutInternalApi +object POCircularProgressIndicator { + + @Composable + fun Small(color: Color) = CircularProgressIndicator( + modifier = Modifier.size(18.dp), + strokeWidth = 1.dp, + color = color + ) + + @Composable + fun Medium(color: Color) = CircularProgressIndicator( + modifier = Modifier.size(28.dp), + strokeWidth = 3.dp, + color = color + ) +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POText.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POText.kt new file mode 100644 index 00000000..ea073315 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/component/POText.kt @@ -0,0 +1,68 @@ +package com.processout.sdk.ui.core.component + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.style.POTextStyle +import com.processout.sdk.ui.core.theme.ProcessOutTheme +import com.processout.sdk.ui.core.theme.toTextStyle + +/** @suppress */ +@ProcessOutInternalApi +object POText { + + @Composable + operator fun invoke( + text: String, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + style: TextStyle = ProcessOutTheme.typography.fixed.body, + fontStyle: FontStyle? = null, + textAlign: TextAlign? = null, + onTextLayout: (TextLayoutResult) -> Unit = {}, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1 + ) = Text( + text = text, + modifier = modifier, + color = color, + style = style, + fontStyle = fontStyle, + textAlign = textAlign, + onTextLayout = onTextLayout, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines + ) + + @Immutable + data class Style( + val color: Color, + val textStyle: TextStyle + ) + + @Composable + fun custom(style: POTextStyle) = Style( + color = colorResource(id = style.colorResId), + textStyle = style.type.toTextStyle() + ) +} + +@Preview(showBackground = true) +@Composable +internal fun POTextPreview() { + POText(text = "ProcessOut Payment") +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBackgroundStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBackgroundStyle.kt new file mode 100644 index 00000000..76e2078a --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBackgroundStyle.kt @@ -0,0 +1,13 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POBackgroundStyle( + @ColorRes + val normal: Int, + @ColorRes + val success: Int +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBorderStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBorderStyle.kt new file mode 100644 index 00000000..f4c39339 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POBorderStyle.kt @@ -0,0 +1,13 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POBorderStyle( + val radiusDp: Int, + val widthDp: Int, + @ColorRes + val colorResId: Int +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POTextStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POTextStyle.kt new file mode 100644 index 00000000..bf941618 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POTextStyle.kt @@ -0,0 +1,12 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POTextStyle( + @ColorRes + val colorResId: Int, + val type: POType +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POType.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POType.kt new file mode 100644 index 00000000..bdc99a08 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/POType.kt @@ -0,0 +1,43 @@ +package com.processout.sdk.ui.core.style + +import android.os.Parcelable +import androidx.annotation.FontRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POType( + @FontRes + val fontResId: Int? = null, + val weight: Weight, + val italic: Boolean = false, + val textSizeSp: Int, + val lineHeightSp: Int +) : Parcelable { + + @Parcelize + enum class Weight(val value: Int) : Parcelable { + THIN(100), + EXTRA_LIGHT(200), + LIGHT(300), + NORMAL(400), + MEDIUM(500), + SEMI_BOLD(600), + BOLD(700), + EXTRA_BOLD(800), + BLACK(900) + } + + object Fixed { + val body = POType(weight = Weight.NORMAL, textSizeSp = 16, lineHeightSp = 24) + val bodyCompact = POType(weight = Weight.NORMAL, textSizeSp = 16, lineHeightSp = 20) + val label = POType(weight = Weight.NORMAL, textSizeSp = 14, lineHeightSp = 18) + val labelHeading = POType(weight = Weight.MEDIUM, textSizeSp = 14, lineHeightSp = 18) + val button = POType(weight = Weight.MEDIUM, textSizeSp = 14, lineHeightSp = 14) + val caption = POType(weight = Weight.NORMAL, textSizeSp = 12, lineHeightSp = 16) + } + + object Medium { + val title = POType(weight = Weight.MEDIUM, textSizeSp = 20, lineHeightSp = 28) + val subtitle = POType(weight = Weight.MEDIUM, textSizeSp = 18, lineHeightSp = 24) + } +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonDefaults.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonDefaults.kt new file mode 100644 index 00000000..dec209a6 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonDefaults.kt @@ -0,0 +1,6 @@ +package com.processout.sdk.ui.core.style.button + +object POButtonDefaults { + const val PADDING_HORIZONTAL_DP = 24 + const val PADDING_VERTICAL_DP = 8 +} diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonHighlightedStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonHighlightedStyle.kt new file mode 100644 index 00000000..9d4f38f0 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonHighlightedStyle.kt @@ -0,0 +1,15 @@ +package com.processout.sdk.ui.core.style.button + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POButtonHighlightedStyle( + @ColorRes + val textColorResId: Int, + @ColorRes + val borderColorResId: Int, + @ColorRes + val backgroundColorResId: Int +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStateStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStateStyle.kt new file mode 100644 index 00000000..3e80ec6c --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStateStyle.kt @@ -0,0 +1,18 @@ +package com.processout.sdk.ui.core.style.button + +import android.os.Parcelable +import androidx.annotation.ColorRes +import com.processout.sdk.ui.core.style.POBorderStyle +import com.processout.sdk.ui.core.style.POTextStyle +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POButtonStateStyle( + val text: POTextStyle, + val border: POBorderStyle, + @ColorRes + val backgroundColorResId: Int, + val elevationDp: Int, + val paddingHorizontalDp: Int = POButtonDefaults.PADDING_HORIZONTAL_DP, + val paddingVerticalDp: Int = POButtonDefaults.PADDING_VERTICAL_DP +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStyle.kt new file mode 100644 index 00000000..b7cb3ef1 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/button/POButtonStyle.kt @@ -0,0 +1,14 @@ +package com.processout.sdk.ui.core.style.button + +import android.os.Parcelable +import androidx.annotation.ColorRes +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POButtonStyle( + val normal: POButtonStateStyle, + val disabled: POButtonStateStyle, + val highlighted: POButtonHighlightedStyle, + @ColorRes + val progressIndicatorColorResId: Int +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/dropdown/ExposedDropdownStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/dropdown/ExposedDropdownStyle.kt new file mode 100644 index 00000000..35140c23 --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/dropdown/ExposedDropdownStyle.kt @@ -0,0 +1,9 @@ +package com.processout.sdk.ui.core.style.dropdown + +import com.processout.sdk.ui.core.style.input.POInputFieldStyle +import com.processout.sdk.ui.core.style.input.POInputStyle + +internal data class ExposedDropdownStyle( + val input: POInputStyle? = null, + val dropdownMenu: POInputFieldStyle? = null +) diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputFieldStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputFieldStyle.kt new file mode 100644 index 00000000..d86d6acc --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputFieldStyle.kt @@ -0,0 +1,19 @@ +package com.processout.sdk.ui.core.style.input + +import android.os.Parcelable +import androidx.annotation.ColorRes +import com.processout.sdk.ui.core.style.POBorderStyle +import com.processout.sdk.ui.core.style.POTextStyle +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POInputFieldStyle( + val text: POTextStyle, + @ColorRes + val hintTextColorResId: Int, + @ColorRes + val backgroundColorResId: Int, + @ColorRes + val controlsTintColorResId: Int, + val border: POBorderStyle +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStateStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStateStyle.kt new file mode 100644 index 00000000..ae9a39cc --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStateStyle.kt @@ -0,0 +1,12 @@ +package com.processout.sdk.ui.core.style.input + +import android.os.Parcelable +import com.processout.sdk.ui.core.style.POTextStyle +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POInputStateStyle( + val title: POTextStyle, + val field: POInputFieldStyle, + val description: POTextStyle +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStyle.kt new file mode 100644 index 00000000..156aecfa --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/input/POInputStyle.kt @@ -0,0 +1,10 @@ +package com.processout.sdk.ui.core.style.input + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class POInputStyle( + val normal: POInputStateStyle, + val error: POInputStateStyle +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStateStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStateStyle.kt new file mode 100644 index 00000000..6e81463c --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStateStyle.kt @@ -0,0 +1,13 @@ +package com.processout.sdk.ui.core.style.radio + +import android.os.Parcelable +import androidx.annotation.ColorRes +import com.processout.sdk.ui.core.style.POTextStyle +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PORadioButtonStateStyle( + @ColorRes + val knobColorResId: Int, + val text: POTextStyle +) : Parcelable diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStyle.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStyle.kt new file mode 100644 index 00000000..a33ceafb --- /dev/null +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/style/radio/PORadioButtonStyle.kt @@ -0,0 +1,17 @@ +package com.processout.sdk.ui.core.style.radio + +import android.os.Parcelable +import androidx.annotation.DrawableRes +import com.processout.sdk.ui.core.style.POTextStyle +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PORadioButtonStyle( + val title: POTextStyle, + val normal: PORadioButtonStateStyle, + val selected: PORadioButtonStateStyle, + val error: PORadioButtonStateStyle, + val errorDescription: POTextStyle, + @DrawableRes + val knobDrawableResId: Int? = null +) : 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 0d118b6d..076be0ce 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 @@ -9,87 +9,123 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi @ProcessOutInternalApi @Immutable data class POColors( - val surfaceBackground: Color, - val surfaceLevel1: Color, - val surfaceNeutral: Color, - val surfaceSuccess: Color, - val surfaceWarning: Color, - val surfaceError: Color, - val borderDefault: Color, - val borderDivider: Color, - val borderSubtle: Color, - val textPrimary: Color, - val textSecondary: Color, - val textTertiary: Color, - val textMuted: Color, - val textDisabled: Color, - val textOnColor: Color, - val textSuccess: Color, - val textWarning: Color, - val textError: Color, - val actionPrimaryDefault: Color, - val actionPrimaryPressed: Color, - val actionPrimaryDisabled: Color, - val actionSecondaryDefault: Color, - val actionSecondaryPressed: Color, - val actionBorderSelected: Color, - val actionBorderDisabled: Color -) + val text: Text, + val action: Action, + val surface: Surface, + val border: Border +) { + @Immutable + data class Text( + val primary: Color, + val secondary: Color, + val tertiary: Color, + val muted: Color, + val disabled: Color, + val onColor: Color, + val success: Color, + val warning: Color, + val error: Color + ) + + @Immutable + data class Action( + val primaryDefault: Color, + val primaryPressed: Color, + val primaryDisabled: Color, + val secondaryDefault: Color, + val secondaryPressed: Color, + val borderSelected: Color, + val borderDisabled: Color + ) + + @Immutable + data class Surface( + val background: Color, + val level1: Color, + val neutral: Color, + val success: Color, + val warning: Color, + val error: Color + ) + + @Immutable + data class Border( + val default: Color, + val divider: Color, + val subtle: Color + ) +} internal val LightColorPalette = POColors( - surfaceBackground = Color(0xFFFFFFFF), - surfaceLevel1 = Color(0xFFFCFCFD), - surfaceNeutral = Color(0xFFF7F7F8), - surfaceSuccess = Color(0xFFD8F8E5), - surfaceWarning = Color(0xFFFDEBE3), - surfaceError = Color(0xFFFAD1D4), - borderDefault = Color(0xFF8D8D95), - borderDivider = Color(0xFFF2F2F3), - borderSubtle = Color(0xFFE5E5E7), - textPrimary = Color(0xFF121212), - textSecondary = Color(0xFF313135), - textTertiary = Color(0xFF4E4E55), - textMuted = Color(0xFF67676F), - textDisabled = Color(0xFF8D8D95), - textOnColor = Color(0xFFFCFCFC), - textSuccess = Color(0xFF014B21), - textWarning = Color(0xFF742702), - textError = Color(0xFF7B0F17), - actionPrimaryDefault = Color(0xFF2B2A93), - actionPrimaryPressed = Color(0xFF1E1E76), - actionPrimaryDisabled = Color(0xFFE5E5E7), - actionSecondaryDefault = Color(0xFFFFFFFF), - actionSecondaryPressed = Color(0xFFE5E5E7), - actionBorderSelected = Color(0xFF4E4E55), - actionBorderDisabled = Color(0xFFC4C4C8) + text = POColors.Text( + primary = Color(0xFF121212), + secondary = Color(0xFF313135), + tertiary = Color(0xFF4E4E55), + muted = Color(0xFF67676F), + disabled = Color(0xFF8D8D95), + onColor = Color(0xFFFCFCFC), + success = Color(0xFF014B21), + warning = Color(0xFF742702), + error = Color(0xFF7B0F17) + ), + action = POColors.Action( + primaryDefault = Color(0xFF2B2A93), + primaryPressed = Color(0xFF1E1E76), + primaryDisabled = Color(0xFFE5E5E7), + secondaryDefault = Color(0xFFFFFFFF), + secondaryPressed = Color(0xFFE5E5E7), + borderSelected = Color(0xFF4E4E55), + borderDisabled = Color(0xFFC4C4C8) + ), + surface = POColors.Surface( + background = Color(0xFFFFFFFF), + level1 = Color(0xFFFCFCFD), + neutral = Color(0xFFF7F7F8), + success = Color(0xFFD8F8E5), + warning = Color(0xFFFDEBE3), + error = Color(0xFFFAD1D4) + ), + border = POColors.Border( + default = Color(0xFF8D8D95), + divider = Color(0xFFF2F2F3), + subtle = Color(0xFFE5E5E7) + ) ) internal val DarkColorPalette = POColors( - surfaceBackground = Color(0xFF121421), - surfaceLevel1 = Color(0xFF111322), - surfaceNeutral = Color(0xFF181A2A), - surfaceSuccess = Color(0xFF014B21), - surfaceWarning = Color(0xFFA23807), - surfaceError = Color(0xFFC0212B), - borderDefault = Color(0xFF93949F), - borderDivider = Color(0xFF212431), - borderSubtle = Color(0xFF2B2D3A), - textPrimary = Color(0xFFF8F8FB), - textSecondary = Color(0xFFD4D4DD), - textTertiary = Color(0xFFBABAC5), - textMuted = Color(0xFF747581), - textDisabled = Color(0xFF4D4F5B), - textOnColor = Color(0xFFF8F8FB), - textSuccess = Color(0xFFD8F8E5), - textWarning = Color(0xFFFDEBE3), - textError = Color(0xFFFBE9EB), - actionPrimaryDefault = Color(0xFF6A64D8), - actionPrimaryPressed = Color(0xFF4D49C5), - actionPrimaryDisabled = Color(0xFF2B2D3A), - actionSecondaryDefault = Color(0xFF121421), - actionSecondaryPressed = Color(0xFF181A2A), - actionBorderSelected = Color(0xFFBABAC5), - actionBorderDisabled = Color(0xFF363945) + text = POColors.Text( + primary = Color(0xFFF8F8FB), + secondary = Color(0xFFD4D4DD), + tertiary = Color(0xFFBABAC5), + muted = Color(0xFF747581), + disabled = Color(0xFF4D4F5B), + onColor = Color(0xFFF8F8FB), + success = Color(0xFFD8F8E5), + warning = Color(0xFFFDEBE3), + error = Color(0xFFFBE9EB) + ), + action = POColors.Action( + primaryDefault = Color(0xFF6A64D8), + primaryPressed = Color(0xFF4D49C5), + primaryDisabled = Color(0xFF2B2D3A), + secondaryDefault = Color(0xFF121421), + secondaryPressed = Color(0xFF181A2A), + borderSelected = Color(0xFFBABAC5), + borderDisabled = Color(0xFF363945) + ), + surface = POColors.Surface( + background = Color(0xFF121421), + level1 = Color(0xFF111322), + neutral = Color(0xFF181A2A), + success = Color(0xFF014B21), + warning = Color(0xFFA23807), + error = Color(0xFFC0212B) + ), + border = POColors.Border( + default = Color(0xFF93949F), + divider = Color(0xFF212431), + subtle = Color(0xFF2B2D3A) + ) ) internal val LocalPOColors = staticCompositionLocalOf { LightColorPalette } 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 cc2f79d1..9d382e5b 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 @@ -12,7 +12,8 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi data class PODimensions( val paddingSmall: Dp = 8.dp, val paddingMedium: Dp = 16.dp, - val paddingLarge: Dp = 24.dp + val paddingLarge: Dp = 24.dp, + val formComponentHeight: Dp = 44.dp ) internal val LocalPODimensions = staticCompositionLocalOf { PODimensions() } diff --git a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Theme.kt b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/ProcessOutTheme.kt similarity index 59% rename from ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Theme.kt rename to ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/ProcessOutTheme.kt index f0cf9873..3657f28b 100644 --- a/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/Theme.kt +++ b/ui-core/src/main/kotlin/com/processout/sdk/ui/core/theme/ProcessOutTheme.kt @@ -1,3 +1,5 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + package com.processout.sdk.ui.core.theme import androidx.compose.foundation.isSystemInDarkTheme @@ -9,26 +11,27 @@ import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi /** @suppress */ @ProcessOutInternalApi -@Composable -fun ProcessOutTheme( - isDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val colors = if (isDarkTheme) DarkColorPalette else LightColorPalette - val typography = ProcessOutTheme.typography - CompositionLocalProvider( - LocalPOColors provides colors, - LocalPOTypography provides typography, - LocalPOShapes provides ProcessOutTheme.shapes, - LocalPODimensions provides ProcessOutTheme.dimensions +object ProcessOutTheme { + + @Composable + operator fun invoke( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit ) { - ProvideTextStyle(value = typography.fixed.body, content = content) + val colors = if (isDarkTheme) DarkColorPalette else LightColorPalette + CompositionLocalProvider( + LocalPOColors provides colors, + LocalPOTypography provides typography, + LocalPOShapes provides shapes, + LocalPODimensions provides dimensions + ) { + ProvideTextStyle( + value = typography.fixed.body, + content = content + ) + } } -} -/** @suppress */ -@ProcessOutInternalApi -object ProcessOutTheme { val colors: POColors @Composable @ReadOnlyComposable 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 ce988087..220d810c 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 @@ -2,7 +2,6 @@ package com.processout.sdk.ui.core.theme -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -11,16 +10,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.processout.sdk.ui.core.R import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi +import com.processout.sdk.ui.core.style.POType +import com.processout.sdk.ui.core.style.POType.Weight.* private val WorkSans = FontFamily( Font(R.font.work_sans_regular, FontWeight.Normal), @@ -34,6 +35,7 @@ data class POTypography( val fixed: Fixed = Fixed(), val medium: Medium = Medium() ) { + @Immutable data class Fixed( val body: TextStyle = TextStyle( fontFamily = WorkSans, @@ -73,6 +75,7 @@ data class POTypography( ) ) + @Immutable data class Medium( val title: TextStyle = TextStyle( fontFamily = WorkSans, @@ -91,54 +94,66 @@ data class POTypography( internal val LocalPOTypography = staticCompositionLocalOf { POTypography() } -@Preview +internal fun POType.toTextStyle() = TextStyle( + fontFamily = fontResId?.let { FontFamily(Font(it)) } ?: WorkSans, + fontWeight = weight.toFontWeight(), + fontStyle = if (italic) FontStyle.Italic else FontStyle.Normal, + fontSize = textSizeSp.sp, + lineHeight = lineHeightSp.sp +) + +private fun POType.Weight.toFontWeight(): FontWeight = + when (this) { + THIN -> FontWeight.Thin + EXTRA_LIGHT -> FontWeight.ExtraLight + LIGHT -> FontWeight.Light + NORMAL -> FontWeight.Normal + MEDIUM -> FontWeight.Medium + SEMI_BOLD -> FontWeight.SemiBold + BOLD -> FontWeight.Bold + EXTRA_BOLD -> FontWeight.ExtraBold + BLACK -> FontWeight.Black + } + +@Preview(showBackground = true) @Composable -fun TypographyPreview() { +internal fun POTypographyPreview() { Column( modifier = Modifier .fillMaxWidth() - .background(Color.White) .padding(16.dp) ) { Text( text = "Typography Medium Title", - style = LocalPOTypography.current.medium.title, - color = Color.Black + style = LocalPOTypography.current.medium.title ) Text( text = "Typography Medium Subtitle", - style = LocalPOTypography.current.medium.subtitle, - color = Color.Black + style = LocalPOTypography.current.medium.subtitle ) Text( text = "Typography Fixed Body", - style = LocalPOTypography.current.fixed.body, - color = Color.Black + style = LocalPOTypography.current.fixed.body ) Text( text = "Typography Fixed Body Compact", - style = LocalPOTypography.current.fixed.bodyCompact, - color = Color.Black + style = LocalPOTypography.current.fixed.bodyCompact ) Text( text = "Typography Fixed Label", - style = LocalPOTypography.current.fixed.label, - color = Color.Black + style = LocalPOTypography.current.fixed.label ) Text( text = "Typography Fixed Label Heading", - style = LocalPOTypography.current.fixed.labelHeading, - color = Color.Black + style = LocalPOTypography.current.fixed.labelHeading ) Text( text = "Typography Fixed Button", - style = LocalPOTypography.current.fixed.button, - color = Color.Black + style = LocalPOTypography.current.fixed.button ) Text( text = "Typography Fixed Caption", - style = LocalPOTypography.current.fixed.caption, - color = Color.Black + style = LocalPOTypography.current.fixed.caption ) } }