diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c4f18d8..5ccb6d7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,6 +27,7 @@
+
diff --git a/app/src/main/java/co/yml/coreui/MainActivity.kt b/app/src/main/java/co/yml/coreui/MainActivity.kt
index e87d1b1..0139de4 100644
--- a/app/src/main/java/co/yml/coreui/MainActivity.kt
+++ b/app/src/main/java/co/yml/coreui/MainActivity.kt
@@ -13,6 +13,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import co.yml.coreui.core.ui.templates.AppBar
import co.yml.coreui.core.ui.theme.CoreUICatalogTheme
+import co.yml.coreui.feature.ytag.ui.YStepperActivity
import co.yml.coreui.feature.ytag.ui.YTagActivity
import co.yml.coreui.ui.R
import co.yml.coreui.ui.presentation.CoreUIComponents
@@ -49,6 +50,15 @@ class MainActivity : ComponentActivity() {
)
)
})
+
+ CoreUIComponents(title = getString(R.string.title_y_stepper), onClick = {
+ startActivity(
+ Intent(
+ this@MainActivity,
+ YStepperActivity::class.java
+ )
+ )
+ })
}
}
}
diff --git a/core/ui/src/androidTest/java/co/yml/coreui/ui/ystepper/StepperViewTest.kt b/core/ui/src/androidTest/java/co/yml/coreui/ui/ystepper/StepperViewTest.kt
new file mode 100644
index 0000000..2becc00
--- /dev/null
+++ b/core/ui/src/androidTest/java/co/yml/coreui/ui/ystepper/StepperViewTest.kt
@@ -0,0 +1,268 @@
+package co.yml.coreui.ui.ystepper
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.test.*
+import androidx.compose.ui.test.junit4.createComposeRule
+import co.yml.coreui.core.ui.ystepper.StepperView
+import co.yml.coreui.core.ui.ystepper.model.StepperIcon
+import co.yml.coreui.core.ui.ystepper.model.StepperModifiers
+import co.yml.coreui.ui.R
+import org.junit.Rule
+import org.junit.Test
+
+class StepperViewTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private fun launchStepper(
+ textView: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ deleteIcon: @Composable (() -> Unit)? = null,
+ stepperModifiers: StepperModifiers = StepperModifiers.Builder().build(),
+ visible: Boolean = true
+ ) {
+ composeTestRule.setContent {
+ StepperView(
+ textView = textView,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon,
+ deleteIcon = deleteIcon,
+ stepperModifier = stepperModifiers,
+ visible = visible
+ )
+ }
+ }
+
+ @Test
+ fun stepper_view_shown() {
+ launchStepper()
+
+ composeTestRule.onNodeWithTag("stepper_view").assertIsDisplayed()
+ }
+
+ @Test
+ fun default_text_view_shown() {
+ launchStepper()
+
+ composeTestRule.onNodeWithTag("text_view_default", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun custom_text_view_shown() {
+ launchStepper(
+ textView = { Text(text = "1") }
+ )
+
+ composeTestRule.onNodeWithTag("text_view_custom", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun text_view_text_shown(){
+ val text = "1"
+ val stepperModifiers = StepperModifiers.Builder().text(text).build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithText(text, useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+
+ @Test
+ fun default_leading_icon_shown() {
+ launchStepper()
+
+ composeTestRule.onNodeWithTag("leading_icon_default", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun custom_leading_icon_shown() {
+ launchStepper(
+ leadingIcon = {
+ IconButton(enabled = true, onClick = {}) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_remove_20px),
+ contentDescription = null
+ )
+ }
+ }
+ )
+
+ composeTestRule.onNodeWithTag("leading_icon_custom", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun default_trailing_icon_shown() {
+ launchStepper()
+
+ composeTestRule.onNodeWithTag("trailing_icon_default", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun custom_trailing_icon_shown() {
+ launchStepper(
+ trailingIcon = {
+ IconButton(onClick = {}) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_add_20px),
+ contentDescription = null
+ )
+ }
+ }
+ )
+
+ composeTestRule.onNodeWithTag("trailing_icon_custom", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun default_delete_icon_shown() {
+ val stepperModifiers = StepperModifiers.Builder().showDeleteIcon(true).build()
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithTag("delete_icon_default", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun custom_delete_icon_shown() {
+ val stepperModifiers = StepperModifiers.Builder().showDeleteIcon(true).build()
+
+ launchStepper(
+ stepperModifiers = stepperModifiers,
+ deleteIcon = {
+ IconButton(onClick = {}) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_delete_20px),
+ contentDescription = null
+ )
+ }
+ }
+ )
+
+ composeTestRule.onNodeWithTag("delete_icon_custom", useUnmergedTree = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun leading_icon_semantics_added() {
+ val semantics = "leading_icon"
+ val stepperModifiers = StepperModifiers.Builder().leadingIcon(
+ StepperIcon(
+ icon = R.drawable.ic_remove_20px,
+ iconTint = Color.Black,
+ semantics = semantics
+ )
+ ).build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithContentDescription(semantics, useUnmergedTree = true)
+ }
+
+ @Test
+ fun trailing_icon_semantics_added() {
+ val semantics = "trailing_icon"
+ val stepperModifiers = StepperModifiers.Builder().trailingIcon(
+ StepperIcon(
+ icon = R.drawable.ic_add_20px,
+ iconTint = Color.Black,
+ semantics = semantics
+ )
+ ).build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithContentDescription(semantics, useUnmergedTree = true)
+ }
+
+ @Test
+ fun delete_icon_semantics_added() {
+ val semantics = "delete_icon"
+ val stepperModifiers = StepperModifiers.Builder().deleteIcon(
+ StepperIcon(
+ icon = R.drawable.ic_add_20px,
+ iconTint = Color.Black,
+ semantics = semantics
+ )
+ ).build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithContentDescription(semantics, useUnmergedTree = true)
+ }
+
+ @Test
+ fun text_view_semantics_added(){
+ val semantics = "count"
+
+ val stepperModifiers = StepperModifiers.Builder().textViewSemantics(semantics).build()
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithContentDescription(semantics, useUnmergedTree = true)
+ }
+
+ @Test
+ fun is_min_value_limit() {
+ val minValue = 1
+ val count = 1
+ val enableLeadingIcon = count > minValue
+
+ val stepperModifiers = StepperModifiers.Builder()
+ .minValue(minValue)
+ .leadingIcon(
+ StepperIcon(
+ R.drawable.ic_remove_20px,
+ iconTint = Color.Black,
+ enable = enableLeadingIcon
+ )
+ )
+ .build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithTag("leading_icon_button_default", useUnmergedTree = true)
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun is_max_value_limit() {
+ val maxValue = 10
+ val count = 10
+ val enableTrailingIcon = count < maxValue
+
+ val stepperModifiers = StepperModifiers.Builder()
+ .minValue(maxValue)
+ .trailingIcon(
+ StepperIcon(
+ R.drawable.ic_remove_20px,
+ iconTint = Color.Black,
+ enable = enableTrailingIcon
+ )
+ )
+ .build()
+
+ launchStepper(stepperModifiers = stepperModifiers)
+
+ composeTestRule.onNodeWithTag("trailing_icon_button_default", useUnmergedTree = true)
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun is_stepper_ui_removed_from_ui_tree() {
+ launchStepper(visible = false)
+
+ composeTestRule.onNodeWithTag("stepper_view").assertDoesNotExist()
+ }
+}
diff --git a/core/ui/src/androidTest/java/co/yml/coreui/ui/ytag/TagViewTest.kt b/core/ui/src/androidTest/java/co/yml/coreui/ui/ytag/TagViewTest.kt
index cc9cac4..b8b1199 100644
--- a/core/ui/src/androidTest/java/co/yml/coreui/ui/ytag/TagViewTest.kt
+++ b/core/ui/src/androidTest/java/co/yml/coreui/ui/ytag/TagViewTest.kt
@@ -22,6 +22,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import co.yml.coreui.core.ui.ytag.TagView
+import co.yml.coreui.core.ui.ytag.model.TagViewData
import co.yml.coreui.core.ui.ytag.model.TagViewModifiers
import org.junit.Rule
import org.junit.Test
@@ -32,8 +33,8 @@ class TagViewTest {
private fun launchYTag(
text: String,
- leadingIcon: @Composable ((Boolean) -> Unit)? = null,
- trailingIcon: @Composable ((Boolean) -> Unit)? = null,
+ leadingIcon: @Composable ((TagViewData) -> Unit)? = null,
+ trailingIcon: @Composable ((TagViewData) -> Unit)? = null,
tagViewModifiers: TagViewModifiers = TagViewModifiers.Builder().build(),
enabled: Boolean = true
) {
diff --git a/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/StepperView.kt b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/StepperView.kt
new file mode 100644
index 0000000..2c4f57e
--- /dev/null
+++ b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/StepperView.kt
@@ -0,0 +1,347 @@
+package co.yml.coreui.core.ui.ystepper
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ConstraintLayout
+import co.yml.coreui.core.ui.ystepper.model.StepperIcon
+import co.yml.coreui.core.ui.ystepper.model.StepperModifiers
+import co.yml.coreui.ui.R
+
+@Composable
+fun StepperView(
+ textView: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
+ deleteIcon: @Composable (() -> Unit)? = null,
+ visible: Boolean = true,
+ stepperModifier: StepperModifiers = StepperModifiers.Builder().build(),
+) {
+ val context = LocalContext.current
+ with(stepperModifier) {
+ var modifiers = if (width == Dp.Unspecified) {
+ Modifier.fillMaxWidth()
+ } else {
+ Modifier.width(width)
+ }
+
+ modifiers = if (height == Dp.Unspecified) {
+ modifiers.then(Modifier.fillMaxWidth())
+ } else {
+ modifiers.then(Modifier.height(height))
+ }
+
+ modifiers = Modifier
+ .width(width)
+ .height(height)
+ .run {
+ if (enableBorder) {
+ border(
+ width = borderWidth,
+ color = borderColor,
+ shape = shape
+ )
+ } else {
+ background(color = backgroundColor, shape = shape)
+ }
+ }
+ .clickable {
+ }
+ .defaultMinSize(minWidth = minWidth, minHeight = minHeight)
+ .padding(containerPaddingValues)
+ .background(
+ color = backgroundColor,
+ shape = shape
+ )
+
+ if (visible) {
+ Surface(
+ shadowElevation = shadowElevation,
+ tonalElevation = tonalElevation,
+ shape = shape,
+ modifier = Modifier
+ .testTag("stepper_view")
+ .semantics {
+ this.contentDescription = stepperViewSemantics ?: context.getString(R.string.stepper_view_accessibility)
+ }
+ ) {
+ ConstraintLayout(
+ modifier = modifiers
+ ) {
+ val (leading_icon, text_view, trailing_icon) = createRefs()
+
+ //Leading Icon
+ if (showDeleteIcon) {
+ //Delete Icon
+ deleteIcon?.let {
+ Box(
+ modifier = Modifier
+ .testTag("delete_icon_custom")
+ .constrainAs(leading_icon) {
+ start.linkTo(parent.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ deleteIcon.invoke()
+ }
+ } ?: kotlin.run {
+ Box(
+ modifier = Modifier
+ .testTag("delete_icon_default")
+ .constrainAs(leading_icon) {
+ start.linkTo(parent.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ IconButton(enabled = stepperModifier.deleteIcon.enable, onClick = {
+ stepperModifier.deleteIcon.onClickListener.invoke()
+ }) {
+ Icon(
+ painter = painterResource(id = stepperModifier.deleteIcon.icon ?: R.drawable.ic_delete_20px),
+ tint = stepperModifier.deleteIcon.iconTint,
+ contentDescription = stepperModifier.deleteIcon.semantics ?: stringResource(
+ id = R.string.ic_delete_accessibility
+ )
+ )
+ }
+ }
+ }
+ } else {
+ leadingIcon?.let {
+ Box(
+ modifier = Modifier
+ .testTag("leading_icon_custom")
+ .constrainAs(leading_icon) {
+ start.linkTo(parent.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ leadingIcon.invoke()
+ }
+ } ?: kotlin.run {
+ Box(
+ modifier = Modifier
+ .testTag("leading_icon_default")
+ .constrainAs(leading_icon) {
+ start.linkTo(parent.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ IconButton(enabled = stepperModifier.leadingIcon.enable, onClick = {
+ stepperModifier.leadingIcon.onClickListener.invoke()
+ }, modifier = Modifier.testTag("leading_icon_button_default")) {
+ Icon(
+ painter = painterResource(id = stepperModifier.leadingIcon.icon ?: R.drawable.ic_remove_20px),
+ tint = stepperModifier.leadingIcon.iconTint,
+ contentDescription = stepperModifier.leadingIcon.semantics ?: stringResource(
+ id = R.string.ic_remove_accessibility
+ )
+ )
+ }
+ }
+ }
+ }
+
+ //Text view
+ textView?.let {
+ Box(modifier = Modifier
+ .testTag("text_view_custom")
+ .constrainAs(text_view) {
+ start.linkTo(leading_icon.end)
+ end.linkTo(trailing_icon.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+// width = Dimension.fillToConstraints
+ }) {
+ textView.invoke()
+ }
+ } ?: kotlin.run {
+ Box(modifier = Modifier
+ .testTag("text_view_default")
+ .constrainAs(text_view) {
+ start.linkTo(leading_icon.end)
+ end.linkTo(trailing_icon.start)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+// width = Dimension.fillToConstraints
+ }) {
+ Text(
+ text = text,
+ color = textColor,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ fontStyle = fontStyle,
+ letterSpacing = letterSpacing,
+ modifier = Modifier
+ .padding(
+ textPadding
+ )
+ .semantics {
+ this.contentDescription = textViewSemantics
+ },
+ style = style,
+ textAlign = textAlign,
+ lineHeight = lineHeight,
+ overflow = overflow,
+ softWrap = softWrap,
+ maxLines = maxLines,
+ onTextLayout = onTextLayout
+ )
+ }
+ }
+
+ //Trailing Icon
+ trailingIcon?.let {
+ Box(
+ modifier = Modifier
+ .testTag("trailing_icon_custom")
+ .constrainAs(trailing_icon) {
+ end.linkTo(parent.end)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ trailingIcon.invoke()
+ }
+ } ?: kotlin.run {
+ Box(
+ modifier = Modifier
+ .testTag("trailing_icon_default")
+ .constrainAs(trailing_icon) {
+ end.linkTo(parent.end)
+ top.linkTo(parent.top)
+ bottom.linkTo(parent.bottom)
+ }
+ ) {
+ IconButton(enabled = stepperModifier.trailingIcon.enable, onClick = {
+ stepperModifier.trailingIcon.onClickListener.invoke()
+ },modifier = Modifier.testTag("trailing_icon_button_default")) {
+ Icon(
+ painter = painterResource(id = stepperModifier.trailingIcon.icon ?: R.drawable.ic_add_20px),
+ tint = stepperModifier.trailingIcon.iconTint,
+ contentDescription = stepperModifier.trailingIcon.semantics ?: stringResource(
+ id = R.string.ic_add_accessibility
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(name = "Default Stepper")
+@Composable
+fun DefaultStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("1")
+ .textColor(Color.Black)
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
+
+@Preview(name = "Capsule Stepper")
+@Composable
+fun CapsuleStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("1")
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
+
+@Preview(name = "Capsule Stepper with border")
+@Composable
+fun BorderStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("1")
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .enableBorder(true)
+ .borderColor(Color.Red)
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
+
+@Preview(name = "Capsule Stepper with background")
+@Composable
+fun BackgroundStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("1")
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .backgroundColor(Color.Yellow)
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
+
+@Preview(name = "Capsule Stepper with delete icon")
+@Composable
+fun DeleteIconStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("1")
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .showDeleteIcon(true)
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
+
+@Preview(name = "Capsule Stepper with custom icons")
+@Composable
+fun CustomIconStepper() {
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(120.dp)
+ .height(36.dp)
+ .text("5")
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .leadingIcon(
+ leadingIcon = StepperIcon(icon = android.R.drawable.star_on, iconTint = Color.Black)
+ )
+ .trailingIcon(trailingIcon = StepperIcon(icon = android.R.drawable.star_off, iconTint = Color.Black))
+ .build()
+
+ StepperView(stepperModifier = stepperModifiers)
+}
diff --git a/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperIcon.kt b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperIcon.kt
new file mode 100644
index 0000000..1918ece
--- /dev/null
+++ b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperIcon.kt
@@ -0,0 +1,12 @@
+package co.yml.coreui.core.ui.ystepper.model
+
+import androidx.compose.ui.graphics.Color
+import co.yml.coreui.core.ui.theme.CoreUICatalogTheme
+
+class StepperIcon(
+ val icon: Int ?=null,
+ val iconTint: Color,
+ val onClickListener: () -> Unit = {},
+ val enable: Boolean = true,
+ val semantics: String? =null
+)
diff --git a/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperModifiers.kt b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperModifiers.kt
new file mode 100644
index 0000000..8d39663
--- /dev/null
+++ b/core/ui/src/main/java/co/yml/coreui/core/ui/ystepper/model/StepperModifiers.kt
@@ -0,0 +1,251 @@
+package co.yml.coreui.core.ui.ystepper.model
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+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.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import co.yml.coreui.ui.R
+
+/**
+ * [StepperModifiers] Represents immutable collection of modifier elements that decorate or add behavior to Stepper elements.
+ * - If a parameter is explicitly set here then that parameter will always be used.
+ * - If a parameter is not set then the corresponding default value will be used
+ *
+ * @param minWidth define a default min width of Stepper
+ * @param minHeight define a default min height of Stepper
+ * @param width define width of Stepper
+ * @param height define height of Stepper
+ * @param textColor apply color to the text
+ * @param fontSize define fontSize of the text element
+ * @param fontStyle define fontStyle of the text element
+ * @param fontFamily define fontStyle of the text element
+ * @param letterSpacing the amount of space to add between each letter in the text element
+ * @param textDecoration the decorations to paint on the text element
+ * @param textAlign the alignment of the text within the lines of the paragraph
+ * @param lineHeight line height for the Paragraph in TextUnit uni
+ * @param overflow how visual overflow should be handled.
+ * @param softWrap whether the text should break at soft line breaks.
+ * @param maxLines an optional maximum number of lines for the text to span, wrapping if necessary.
+ * @param onTextLayout callback that is executed when a new text layout is calculated.
+ * @param style style configuration for the text such as color, font, line height etc.
+ * @param enableBorder enable border for Stepper
+ * @param borderWidth define borderWidth of the Stepper
+ * @param borderColor define borderColor of the Stepper
+ * @param backgroundColor define backgroundColor of the Stepper
+ * @param textPadding define padding for Stepper text component
+ * @param shape defines the shape of the Stepper
+ * @param tonalElevation When color is ColorScheme.surface, a higher the elevation will result in a darker color in light theme and lighter color in dark theme.
+ * @param shadowElevation The size of the shadow below the surface.
+ * @param containerPaddingValues define padding for Stepper
+ * @param onClick perform click event
+ * @param textViewSemantics add content description for Stepper view
+ */
+data class StepperModifiers (
+ val minWidth: Dp,
+ val minHeight: Dp,
+ val width: Dp,
+ val height: Dp,
+ val text: String,
+ val textColor: Color,
+ val fontSize: TextUnit,
+ val fontStyle: FontStyle,
+ val fontWeight: FontWeight,
+ val fontFamily: FontFamily,
+ val letterSpacing: TextUnit,
+ val textDecoration: TextDecoration?,
+ val textAlign: TextAlign,
+ val lineHeight: TextUnit,
+ val overflow: TextOverflow,
+ val softWrap: Boolean,
+ val maxLines: Int,
+ val onTextLayout: (TextLayoutResult) -> Unit,
+ val style: TextStyle,
+ val enableBorder: Boolean,
+ val borderWidth: Dp,
+ val borderColor: Color,
+ val backgroundColor: Color,
+ val textPadding: PaddingValues,
+ val shape: Shape,
+ val tonalElevation: Dp,
+ val shadowElevation: Dp,
+ val containerPaddingValues: PaddingValues,
+ val onClick: () -> Unit,
+ val leadingIcon: StepperIcon,
+ val trailingIcon: StepperIcon,
+ val deleteIcon: StepperIcon,
+ val minValue: Int,
+ val maxValue: Int,
+ val stepValue: Int,
+ val showDeleteIcon: Boolean,
+ val textViewSemantics: String,
+ val stepperViewSemantics: String?){
+
+ class Builder {
+ private var minWidth: Dp = 80.dp
+ private var minHeight: Dp = 32.dp
+ private var width: Dp = Dp.Unspecified
+ private var height: Dp = Dp.Unspecified
+ private var text: String = ""
+ private var textColor: Color = Color.Black
+ private var fontSize: TextUnit = 12.sp
+ private var fontStyle: FontStyle = FontStyle.Normal
+ private var fontWeight: FontWeight = FontWeight.Normal
+ private var fontFamily: FontFamily = FontFamily.Default
+ private var letterSpacing: TextUnit = TextUnit.Unspecified
+ private var textDecoration: TextDecoration? = null
+ private var textAlign: TextAlign = TextAlign.Center
+ private var lineHeight: TextUnit = TextUnit.Unspecified
+ private var overflow: TextOverflow = TextOverflow.Clip
+ private var softWrap: Boolean = true
+ private var onTextLayout: (TextLayoutResult) -> Unit = {}
+ private var maxLines: Int = Int.MAX_VALUE
+ private var style = TextStyle()
+ private var enableBorder: Boolean = false
+ private var borderWidth: Dp = 1.dp
+ private var borderColor: Color = Color.Black
+ private var backgroundColor: Color = Color.White
+ private var textPadding: PaddingValues = PaddingValues(horizontal = 8.dp)
+ private var shape: Shape = RectangleShape
+ private var tonalElevation: Dp = 0.dp
+ private var shadowElevation: Dp = 0.dp
+ private var containerPaddingValues: PaddingValues = PaddingValues(horizontal = 4.dp)
+ private var onClick: () -> Unit = {}
+ private var leadingIcon: StepperIcon = StepperIcon(icon = R.drawable.ic_remove_20px, iconTint = Color.Black, onClickListener = {})
+ private var trailingIcon: StepperIcon = StepperIcon(icon = R.drawable.ic_add_20px, iconTint = Color.Black, onClickListener = {})
+ private var deleteIcon: StepperIcon = StepperIcon(icon = R.drawable.ic_delete_20px, iconTint = Color.Black, onClickListener = {})
+ private var minValue: Int = 1
+ private var maxValue: Int = Int.MAX_VALUE
+ private var stepValue: Int = 1
+ private var showDeleteIcon = false
+ private var textViewSemantics: String = text
+ private var stepperViewSemantics: String? = null
+ fun minWidth(minWidth: Dp) = apply { this.minWidth = minWidth }
+
+ fun minHeight(minHeight: Dp) = apply { this.minHeight = minHeight }
+
+ fun width(width: Dp) = apply { this.width = width }
+ fun height(height: Dp) = apply { this.height = height }
+
+ fun text(text: String) = apply { this.text = text }
+
+ fun textColor(textColor: Color) = apply { this.textColor = textColor }
+
+ fun fontSize(fontSize: TextUnit) = apply { this.fontSize = fontSize }
+
+ fun fontStyle(fontStyle: FontStyle) = apply { this.fontStyle = fontStyle }
+
+ fun fontWeight(fontWeight: FontWeight) = apply { this.fontWeight = fontWeight }
+
+ fun fontFamily(fontFamily: FontFamily) = apply { this.fontFamily = fontFamily }
+ fun letterSpacing(letterSpacing: TextUnit) = apply { this.letterSpacing = letterSpacing }
+ fun textDecoration(textDecoration: TextDecoration) =
+ apply { this.textDecoration = textDecoration }
+
+ fun textAlign(textAlign: TextAlign) = apply { this.textAlign = textAlign }
+
+ fun lineHeight(lineHeight: TextUnit) = apply { this.lineHeight = lineHeight }
+
+ fun overFlow(overflow: TextOverflow) = apply { this.overflow = overflow }
+
+ fun softWrap(softWrap: Boolean) = apply { this.softWrap = softWrap }
+
+ fun maxLines(maxLines: Int) = apply { this.maxLines }
+
+ fun onTextLayout(onTextLayout: (TextLayoutResult) -> Unit) =
+ apply { this.onTextLayout = onTextLayout }
+
+ fun style(style: TextStyle) = apply { this.style = style }
+
+ fun enableBorder(enableBorder: Boolean) = apply { this.enableBorder = enableBorder }
+
+ fun borderWidth(borderWidth: Dp) = apply { this.borderWidth = borderWidth }
+
+ fun borderColor(borderColor: Color) = apply { this.borderColor = borderColor }
+
+ fun backgroundColor(backgroundColor: Color) =
+ apply { this.backgroundColor = backgroundColor }
+
+ fun textPadding(textPadding: PaddingValues) = apply { this.textPadding = textPadding }
+
+ fun shape(shape: Shape) = apply { this.shape = shape }
+
+ fun tonalElevation(tonalElevation: Dp) = apply { this.tonalElevation = tonalElevation }
+
+ fun shadowElevation(shadowElevation: Dp) = apply { this.shadowElevation = shadowElevation }
+ fun containerPaddingValues(paddingValues: PaddingValues) =
+ apply { this.containerPaddingValues = paddingValues }
+
+ fun onCLick(onClick: () -> Unit) = apply { this.onClick = onClick }
+
+ fun leadingIcon(leadingIcon: StepperIcon) = apply { this.leadingIcon = leadingIcon }
+
+ fun trailingIcon(trailingIcon: StepperIcon) = apply { this.trailingIcon = trailingIcon }
+
+ fun deleteIcon(deleteIcon: StepperIcon) = apply { this.deleteIcon = deleteIcon }
+
+ fun minValue(minValue: Int) = apply { this.minValue = minValue }
+
+ fun maxValue(maxValue: Int) = apply { this.maxValue = maxValue }
+
+ fun stepValue(stepValue: Int) = apply { this.stepValue = stepValue }
+
+ fun showDeleteIcon(showDeleteIcon: Boolean) = apply { this.showDeleteIcon = showDeleteIcon }
+
+ fun textViewSemantics(textViewSemantics: String) = apply { this.textViewSemantics = textViewSemantics }
+
+ fun stepperViewSemantics(stepperViewSemantics: String) = apply { this.stepperViewSemantics = stepperViewSemantics }
+
+ fun build() = StepperModifiers(
+ minWidth,
+ minHeight,
+ width,
+ height,
+ text,
+ textColor,
+ fontSize,
+ fontStyle,
+ fontWeight,
+ fontFamily,
+ letterSpacing,
+ textDecoration,
+ textAlign,
+ lineHeight,
+ overflow,
+ softWrap,
+ maxLines,
+ onTextLayout,
+ style,
+ enableBorder,
+ borderWidth,
+ borderColor,
+ backgroundColor,
+ textPadding,
+ shape,
+ tonalElevation,
+ shadowElevation,
+ containerPaddingValues,
+ onClick,
+ leadingIcon,
+ trailingIcon,
+ deleteIcon,
+ minValue,
+ maxValue,
+ stepValue,
+ showDeleteIcon,
+ textViewSemantics,
+ stepperViewSemantics
+ )
+ }
+}
diff --git a/core/ui/src/main/res/drawable/ic_add_20px.xml b/core/ui/src/main/res/drawable/ic_add_20px.xml
new file mode 100644
index 0000000..2211570
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_add_20px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_delete_20px.xml b/core/ui/src/main/res/drawable/ic_delete_20px.xml
new file mode 100644
index 0000000..f900c92
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_delete_20px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_remove_20px.xml b/core/ui/src/main/res/drawable/ic_remove_20px.xml
new file mode 100644
index 0000000..7fc4a6f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_remove_20px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml
index 5c92fc8..8191ec5 100644
--- a/core/ui/src/main/res/values/strings.xml
+++ b/core/ui/src/main/res/values/strings.xml
@@ -4,4 +4,10 @@
Tags
more
Tag view container
+ Stepper
+ Add
+ Remove
+ Remove
+ Stepper View
+
diff --git a/feature/ytag/src/main/java/co/yml/coreui/feature/ytag/ui/YStepperActivity.kt b/feature/ytag/src/main/java/co/yml/coreui/feature/ytag/ui/YStepperActivity.kt
new file mode 100644
index 0000000..c333657
--- /dev/null
+++ b/feature/ytag/src/main/java/co/yml/coreui/feature/ytag/ui/YStepperActivity.kt
@@ -0,0 +1,331 @@
+package co.yml.coreui.feature.ytag.ui
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import co.yml.coreui.core.ui.templates.AppBarWithBackButton
+import co.yml.coreui.core.ui.theme.CoreUICatalogTheme
+import co.yml.coreui.core.ui.ystepper.StepperView
+import co.yml.coreui.core.ui.ystepper.model.StepperIcon
+import co.yml.coreui.core.ui.ystepper.model.StepperModifiers
+import co.yml.coreui.ui.R
+import dagger.hilt.android.AndroidEntryPoint
+
+@ExperimentalMaterial3Api
+@AndroidEntryPoint
+class YStepperActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ CoreUICatalogTheme {
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ containerColor = CoreUICatalogTheme.colors.background,
+ topBar = {
+ AppBarWithBackButton(
+ stringResource(id = R.string.title_y_stepper),
+ onBackPressed = {
+ onBackPressed()
+ })
+ }) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ .padding(it)
+ .padding(PaddingValues(horizontal = dimensionResource(id = R.dimen.padding_normal)))
+ ) {
+ Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.padding_normal_medium)))
+
+ DefaultStepper()
+
+ Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.padding_normal)))
+
+ CustomStepper()
+
+ Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.padding_normal)))
+
+ ModifiedBuilderStepper()
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun DefaultStepper() {
+ val minValue = 1
+ val maxValue = 10
+
+ var count by remember {
+ mutableStateOf(minValue)
+ }
+
+ var enableLeadingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var enableTrailingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var showDeleteIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var stepperVisibility by remember {
+ mutableStateOf(true)
+ }
+
+ enableLeadingIcon = count > minValue
+ showDeleteIcon = count <= minValue
+ enableTrailingIcon = count < maxValue
+
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(140.dp)
+ .height(48.dp)
+ .enableBorder(true)
+ .borderColor(Color.Black)
+ .borderWidth(1.dp)
+ .text(count.toString())
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .showDeleteIcon(showDeleteIcon)
+ .leadingIcon(
+ StepperIcon(
+ enable = enableLeadingIcon,
+ iconTint = Color.Black,
+ onClickListener = {
+ count -= 1
+ },
+ )
+ )
+
+ .trailingIcon(
+ StepperIcon(
+ enable = enableTrailingIcon,
+ iconTint = Color.Black,
+ onClickListener = {
+ count += 1
+ })
+ )
+ .deleteIcon(
+ StepperIcon(
+ iconTint = Color.Black,
+ onClickListener = {
+ stepperVisibility = false
+ }
+ )
+ )
+ .build()
+
+ StepperView(
+ visible = stepperVisibility,
+ stepperModifier = stepperModifiers
+ )
+}
+
+@Composable
+fun ModifiedBuilderStepper() {
+ val minValue = 1
+ val maxValue = 10
+
+ var count by remember {
+ mutableStateOf(minValue)
+ }
+
+ var enableLeadingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var enableTrailingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var stepperVisibility by remember {
+ mutableStateOf(true)
+ }
+
+ enableLeadingIcon = count > minValue
+ enableTrailingIcon = count < maxValue
+
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(140.dp)
+ .height(48.dp)
+ .enableBorder(true)
+ .borderColor(Color.Gray)
+ .borderWidth(1.dp)
+ .text(count.toString())
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .leadingIcon(
+ StepperIcon(
+ enable = enableLeadingIcon,
+ icon = R.drawable.ic_remove_20px,
+ iconTint = Color.Black,
+ onClickListener = {
+ count -= 1
+ },
+ semantics = "modified leading icon"
+ )
+ )
+
+ .trailingIcon(
+ StepperIcon(
+ enable = enableTrailingIcon,
+ icon = R.drawable.ic_add_20px,
+ iconTint = Color.Black,
+ onClickListener = {
+ count += 1
+ },
+ semantics = "modified trailing icon"
+ )
+ )
+ .deleteIcon(
+ StepperIcon(
+ icon = R.drawable.ic_delete_20px,
+ iconTint = Color.Black,
+ onClickListener = {
+ stepperVisibility = false
+ },
+ semantics = "modified delete icon"
+ )
+ )
+ .textViewSemantics("modified text view count $count")
+ .stepperViewSemantics("modified stepper view")
+ .build()
+
+ StepperView(
+ visible = stepperVisibility,
+ stepperModifier = stepperModifiers
+ )
+}
+
+@Composable
+fun CustomStepper() {
+ val minValue = 1
+ val maxValue = 10
+ val stepValue = 2
+
+ var count by remember {
+ mutableStateOf(minValue)
+ }
+
+ var enableLeadingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var enableTrailingIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var showDeleteIcon by remember {
+ mutableStateOf(true)
+ }
+
+ var stepperVisibility by remember {
+ mutableStateOf(true)
+ }
+
+ enableLeadingIcon = count > minValue
+ showDeleteIcon = count <= minValue
+ enableTrailingIcon = count < maxValue
+
+ val stepperModifiers = StepperModifiers.Builder()
+ .width(140.dp)
+ .height(48.dp)
+ .backgroundColor(Color.Green)
+ .text(count.toString())
+ .textColor(Color.Black)
+ .shape(CircleShape)
+ .showDeleteIcon(showDeleteIcon)
+ .build()
+
+ StepperView(
+ visible = stepperVisibility,
+ stepperModifier = stepperModifiers,
+ textView = {
+ Text(
+ text = "$count",
+ modifier = Modifier.semantics { this.contentDescription = "Item count: $count" })
+ },
+ leadingIcon = {
+ IconButton(enabled = enableLeadingIcon, onClick = {
+ if (count - stepValue < minValue){
+ count = minValue
+ }else{
+ count -= stepValue
+ }
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_remove_20px),
+ contentDescription = "Leading"
+ )
+ }
+ },
+ trailingIcon = {
+ IconButton(enabled = enableTrailingIcon, onClick = {
+ if (count + stepValue > maxValue){
+ count = maxValue
+ }else{
+ count += stepValue
+ }
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_add_20px),
+ contentDescription = "Trailing"
+ )
+ }
+ },
+ deleteIcon = {
+ IconButton(enabled = enableTrailingIcon, onClick = {
+ stepperVisibility = false
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_delete_20px),
+ contentDescription = "Delete Item"
+ )
+ }
+ }
+ )
+}
+
+
+@Preview
+@Composable
+fun DefaultStepperPreview(){
+ Column(
+ modifier = Modifier
+ .background(Color.White)
+ .padding(PaddingValues(horizontal = 16.dp))
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+
+ DefaultStepper()
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ CustomStepper()
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ ModifiedBuilderStepper()
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+}