Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.processout.sdk.ui.core.component

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
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.POStrokeStyle

/** @suppress */
@ProcessOutInternalApi
object POStroke {

@Immutable
data class Style(
val width: Dp,
val color: Color,
val dashInterval: Dp? = null
)

@Composable
fun custom(style: POStrokeStyle) = Style(
width = style.widthDp.dp,
color = colorResource(id = style.colorResId),
dashInterval = style.dashIntervalDp?.dp
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package com.processout.sdk.ui.core.component.stepper

import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
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.POBorderStroke
import com.processout.sdk.ui.core.style.POStepperStyle

/** @suppress */
@ProcessOutInternalApi
@Composable
fun POStepIcon(
iconSize: Dp = POStepIcon.DefaultIconSize,
padding: Dp = POStepIcon.DefaultPadding,
style: POStepIcon.Style = POStepIcon.active
) {
val density = LocalDensity.current
val iconRadiusPx = with(density) { iconSize.toPx() / 2 }
val borderWidth = style.border?.width ?: 0.dp
val borderWidthPx = with(density) { borderWidth.toPx() }
val haloWidth = style.halo?.width ?: 0.dp
val haloWidthPx = with(density) { haloWidth.toPx() }
val checkmarkWidth = style.checkmark?.width ?: 0.dp
val checkmarkWidthPx = with(density) { checkmarkWidth.toPx() }

val infiniteTransition = rememberInfiniteTransition()
val animatedHaloWidthPx by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = haloWidthPx,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 800, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Canvas(
modifier = Modifier
.padding(padding)
.requiredSize(size = iconSize)
) {
val center = Offset(x = size.width / 2, y = size.height / 2)
// Halo
if (style.halo != null) {
drawCircle(
color = style.halo.color,
radius = iconRadiusPx + animatedHaloWidthPx,
center = center
)
}
// Icon
drawCircle(
color = style.backgroundColor,
radius = iconRadiusPx,
center = center
)
// Border
if (style.border != null) {
drawCircle(
color = style.border.color,
radius = iconRadiusPx - borderWidthPx / 2,
center = center,
style = Stroke(width = borderWidthPx)
)
}
// Checkmark
if (style.checkmark != null) {
val checkmarkPath = Path().apply {
val scale = 1.3f
val start = Offset(
x = center.x - size.minDimension * 0.15f * scale,
y = center.y
)
val mid = Offset(
x = center.x - size.minDimension * 0.05f * scale,
y = center.y + size.minDimension * 0.15f * scale
)
val end = Offset(
x = center.x + size.minDimension * 0.2f * scale,
y = center.y - size.minDimension * 0.15f * scale
)
moveTo(start.x, start.y)
lineTo(mid.x, mid.y)
lineTo(end.x, end.y)
}
drawPath(
path = checkmarkPath,
color = style.checkmark.color,
style = Stroke(
width = checkmarkWidthPx,
cap = StrokeCap.Round,
join = StrokeJoin.Round
)
)
}
}
}

/** @suppress */
@ProcessOutInternalApi
object POStepIcon {

data class Style(
val backgroundColor: Color,
val border: POBorderStroke?,
val halo: Halo?,
val checkmark: Checkmark?
)

data class Halo(
val width: Dp,
val color: Color
)

data class Checkmark(
val width: Dp,
val color: Color
)

internal val DefaultIconSize: Dp = 24.dp
internal val DefaultPadding: Dp = 6.dp

private val DefaultBorderColor = Color(0xFFA3A3A3)
internal val DefaultCompletedColor = Color(0xFF4CA259)

val pending: Style
@Composable get() = Style(
backgroundColor = Color.Transparent,
border = POBorderStroke(
width = 1.5.dp,
color = DefaultBorderColor
),
halo = null,
checkmark = null
)

val active: Style
@Composable get() = Style(
backgroundColor = Color.White,
border = POBorderStroke(
width = 1.5.dp,
color = DefaultBorderColor
),
halo = Halo(
width = 6.dp,
color = Color.Black.copy(alpha = 0.07f)
),
checkmark = null
)

val completed: Style
@Composable get() = Style(
backgroundColor = DefaultCompletedColor,
border = null,
halo = null,
checkmark = Checkmark(
width = 2.dp,
color = Color.White
)
)

@Composable
fun custom(style: POStepperStyle.IconStyle) = Style(
backgroundColor = colorResource(id = style.backgroundColorResId),
border = style.border?.let {
POBorderStroke(
width = it.widthDp.dp,
color = colorResource(id = it.colorResId)
)
},
halo = style.halo?.let {
Halo(
width = it.widthDp.dp,
color = colorResource(id = it.colorResId)
)
},
checkmark = style.checkmark?.let {
Checkmark(
width = it.widthDp.dp,
color = colorResource(id = it.colorResId)
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.processout.sdk.ui.core.component.stepper

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
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.POStroke
import com.processout.sdk.ui.core.component.POText
import com.processout.sdk.ui.core.component.stepper.POStepper.StepState.*
import com.processout.sdk.ui.core.style.POStepperStyle
import com.processout.sdk.ui.core.theme.ProcessOutTheme.colors
import com.processout.sdk.ui.core.theme.ProcessOutTheme.typography

/** @suppress */
@ProcessOutInternalApi
object POStepper {

data class Step(
val title: String,
val description: String? = null
)

enum class StepState {
PENDING,
ACTIVE,
COMPLETED
}

data class Style(
val pending: StepStyle,
val active: StepStyle,
val completed: StepStyle
)

data class StepStyle(
val title: POText.Style,
val description: POText.Style,
val icon: POStepIcon.Style,
val connector: POStroke.Style
)

val default: Style
@Composable get() = Style(
pending = StepStyle(
title = POText.Style(
color = colors.text.tertiary,
textStyle = typography.s15(FontWeight.Medium)
),
description = POText.Style(
color = colors.text.tertiary,
textStyle = typography.s12(FontWeight.Medium)
),
icon = POStepIcon.pending,
connector = POStroke.Style(
width = 2.dp,
color = Color(0xFFCECECE),
dashInterval = 3.dp
)
),
active = StepStyle(
title = POText.Style(
color = colors.text.primary,
textStyle = typography.s15(FontWeight.Medium)
),
description = POText.Style(
color = colors.text.secondary,
textStyle = typography.s12(FontWeight.Medium)
),
icon = POStepIcon.active,
connector = POStroke.Style(
width = 2.dp,
color = POStepIcon.DefaultCompletedColor,
dashInterval = 3.dp
)
),
completed = StepStyle(
title = POText.Style(
color = colors.text.positive,
textStyle = typography.s15(FontWeight.Medium)
),
description = POText.Style(
color = colors.text.secondary,
textStyle = typography.s12(FontWeight.Medium)
),
icon = POStepIcon.completed,
connector = POStroke.Style(
width = 2.dp,
color = POStepIcon.DefaultCompletedColor
)
)
)

@Composable
fun custom(style: POStepperStyle) = Style(
pending = style.pending.toStepStyle(),
active = style.active.toStepStyle(),
completed = style.completed.toStepStyle()
)

@Composable
private fun POStepperStyle.StepStyle.toStepStyle() = StepStyle(
title = POText.custom(style = title),
description = POText.custom(style = description),
icon = POStepIcon.custom(style = icon),
connector = POStroke.custom(style = connector)
)

internal fun stepStyle(
style: Style,
state: StepState
): StepStyle =
when (state) {
PENDING -> style.pending
ACTIVE -> style.active
COMPLETED -> style.completed
}

internal fun connectorStyle(
style: Style,
states: List<StepState>,
currentStepIndex: Int
): POStroke.Style {
val current = states.getOrNull(index = currentStepIndex)
val next = states.getOrNull(index = currentStepIndex + 1)
return when {
current == COMPLETED && next == COMPLETED -> style.completed.connector
current == COMPLETED && next == ACTIVE -> style.active.connector
else -> style.pending.connector
}
}
}
Loading