Compose look alike Progress Indicator breadcrumb

123 Views Asked by At

I am trying to create a composable that simulates a breadcrumb. Something like this. The current step will have the double circle in a pink color, the visited step will have the normal pink dot, and the no visited, a grey dot. The line logic follows accordingly as the image shows.

I have been experimenting with Canvas, but I never get it right. And also I cannot get "Company" centered...

enter image description here

This is my code. I think I am overdoing it, so please advise how to refactor using Canvas. I am sure it can be done in fewer lines.


@Composable
fun PlatfoBreadCrumb(
    modifier: Modifier = Modifier,
    currentStep: Int,
) {
    Column(
        modifier = modifier
            .fillMaxWidth(),
    ) {
        Row(
            modifier = modifier
                .fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            ProgressStep(
                text = "About You",
                isSelected = currentStep == 1
            )

            ProgressStep(
                text = "Company",
                isSelected = currentStep == 2
            )

            ProgressStep(
                text= "Activity",
                isSelected = currentStep == 3
            )
        }
        Spacer(modifier = Modifier.height(4.dp))
        Box(
            modifier = Modifier
                .fillMaxWidth(),
            contentAlignment = Alignment.CenterStart
        ) {
            Row(
                modifier = modifier
                    .fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {

                repeat(3) { index ->
                    if (index < currentStep - 1) {
                        Line(
                            modifier = Modifier.weight(1f),
                            color = colorResource(id = R.color.primary_50)
                        )
                    }
                    ProgressDots(isSelected = currentStep == index)
                }
            }
        }
    }
}


@Composable
fun Line(
    modifier: Modifier = Modifier,
    color: Color
) {
    Canvas(
        modifier = modifier
    ) {
        drawLine(
            color = color,
            start = Offset.Zero,
            end = Offset(size.width, 0f),
            strokeWidth = 1.dp.toPx()
        )
    }
}

@Composable
fun ProgressStep(
    text: String,
    isSelected: Boolean
) {
    Text(
        text = text,
        color = if(isSelected) colorResource(id = R.color.primary_50) else
            colorResource(id = R.color.neutral_50),
    )
}

@Composable
fun ProgressDots(
    isSelected: Boolean
) {
    if (isSelected) {
        Box(
            modifier = Modifier
                .size(20.dp)
                .border(
                    1.dp,
                    color = colorResource(id = R.color.primary_50),
                    shape = CircleShape,
                ), contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(
                        color = colorResource(id = R.color.primary_50), shape = CircleShape
                    )
            )
        }
    } else {
        Box(
            modifier = Modifier
                .size(20.dp)
                .border(
                    0.dp,
                    color = Color.Transparent,
                    shape = CircleShape,
                ), contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(
                        color = colorResource(id = R.color.neutral_50), shape = CircleShape
                    )
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun BreadCrumbPreview() {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .padding(48.dp),
        color = Color.Transparent
    ) {
        PlatfoBreadCrumb(
            currentStep = 1
        )
    }
}

Thank you for the attention!

1

There are 1 best solutions below

0
JaviMar On

I have been working and came with a solution that in my opinion is not the best one, however, it works.


@Composable
fun PlatfoBreadCrumb(
    modifier: Modifier = Modifier,
    selectedStep: Int,
) {
    Column(
        modifier = modifier
            .fillMaxWidth(),
    ) {
        Row(
            modifier = modifier
                .fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            repeat(3) {
                val text = when(it) {
                    0 -> "About You"
                    1 -> "Company"
                    2 -> "Activity"
                    else -> ""
                }
                ProgressStep(
                    text = text,
                    step = it,
                    selectedStep = selectedStep
                )
            }
        }
        Spacer(modifier = Modifier.height(4.dp))
        Box(
            modifier = Modifier
                .fillMaxWidth(),
            contentAlignment = Alignment.CenterStart
        ) {
            ProgressLine(
                modifier.fillMaxWidth(),
                step = selectedStep
            )
            Row(
                modifier = modifier
                    .fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {
                repeat(3) {
                    ProgressDots(
                        step = it,
                        selectedStep = selectedStep
                    )
                }
            }
        }
    }
}

@Composable
fun ProgressStep(
    text: String,
    step: Int,
    selectedStep: Int,
) {
    val textColor = when(selectedStep) {
        0 -> {
            if(step == 0) colorResource(id = R.color.neutral_95) else
                colorResource(id = R.color.neutral_50)
        }
        1 -> {
            if(step <= 1) colorResource(id = R.color.neutral_95) else
                colorResource(id = R.color.neutral_50)
        }
        2 -> colorResource(id = R.color.neutral_95)
        else -> colorResource(id = R.color.neutral_50)
    }

    val fontWeight = if (selectedStep == step) {
        FontWeight.Bold
    } else {
        FontWeight.Normal
    }

    Text(
        text = text,
        color = textColor,
        fontWeight = fontWeight
    )
}

@Composable
fun ProgressDots(
    step: Int,
    selectedStep: Int,
) {
    val color = when(selectedStep) {
        0 -> {
            if(step == 0) colorResource(id = R.color.primary_50) else
                colorResource(id = R.color.neutral_50)
        }
        1 -> {
            if(step <= 1) colorResource(id = R.color.primary_50) else
                colorResource(id = R.color.neutral_50)
        }
        2 -> colorResource(id = R.color.primary_50)
        else -> colorResource(id = R.color.neutral_50)
    }

    if (selectedStep == step) {
        Box(
            modifier = Modifier
                .size(20.dp)
                .border(
                    1.dp,
                    color = color,
                    shape = CircleShape,
                ), contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(
                        color = color, shape = CircleShape
                    )
            )
        }
    } else {
        Box(
            modifier = Modifier
                .size(20.dp)
                .border(
                    0.dp,
                    color = Color.Transparent,
                    shape = CircleShape,
                ), contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(
                        color = color, shape = CircleShape
                    )
            )
        }
    }
}

@Composable
private fun ProgressLine(
    modifier: Modifier,
    step: Int
) {
    val remain = colorResource(id = R.color.neutral_50)
    val visited = colorResource(id = R.color.primary_50)
    Canvas(
        modifier = modifier
    ) {
        when(step) {
            0 -> {
                drawLine(
                    color = remain,
                    start = Offset(20.dp.toPx(), 0f),
                    end = Offset(size.width - 10, 0f),
                    strokeWidth = 1.dp.toPx())
            }
            1 -> {
                drawLine(
                    color = visited,
                    start = Offset(16.dp.toPx(), 0f),
                    end = Offset(size.width/2 - 10.dp.toPx(), 0f),
                    strokeWidth = 1.dp.toPx())
                drawLine(
                    color = remain,
                    start =Offset(size.width/2 + 10.dp.toPx(), 0f),
                    end = Offset(size.width - 10, 0f),
                    strokeWidth = 1.dp.toPx())
            }
            2 -> {
                drawLine(
                    color = visited,
                    start = Offset(16.dp.toPx(), 0f),
                    end = Offset(size.width - 20.dp.toPx(), 0f),
                    strokeWidth = 1.dp.toPx())
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun BreadCrumbPreview() {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .padding(48.dp),
        color = Color.Transparent
    ) {
        PlatfoBreadCrumb(
            selectedStep = 1
        )
    }
}

Feel free to provide a refactor and optimize this solution. Thanks!!