Amit Kundu
Amit Kundu

Reputation: 119

Custom Modifier.indication loose it's drawing if there is "toggleable/clickable/selectable" modifier in it

So I'm trying to improve the keyboard focus indication for Jetpack Compose components.

I have created a custom class to draw border:

private class MyHighlightIndicationNode(private val interactionSource: InteractionSource) :
    Modifier.Node(), DrawModifierNode {
    private var isFocused = false

    override fun onAttach() {
        coroutineScope.launch {
            var focusCount = 0
            interactionSource.interactions.collect { interaction ->
                when (interaction) {
                    is FocusInteraction.Focus -> focusCount++
                    is FocusInteraction.Unfocus -> focusCount--
                }
                val focused = focusCount > 0
                if (isFocused != focused) {
                    isFocused = focused
                    invalidateDraw()
                }
            }
        }
    }

    override fun ContentDrawScope.draw() {
        drawContent()
        if (isFocused) {
            drawRoundRect(
                color = themedIndicationColor,
                size = size,
                cornerRadius = CornerRadius(12.dp.toPx(), 12.dp.toPx()),
                style = Stroke(width = 2.dp.toPx()),
                alpha = 1.0f
            )
        }
    }
}

object MyHighlightIndication : IndicationNodeFactory {
    override fun create(interactionSource: InteractionSource): DelegatableNode {
        return MyHighlightIndicationNode(interactionSource)
    }

    override fun hashCode(): Int = -1

    override fun equals(other: Any?) = other === this
}

It's from this link

Now, if a Modifier has a toggleable/clickable/selectable interaction, I saw that the border gets removed after the action.

Here is client code:

@Composable
private fun GoodExample7(
    snackbarLauncher: SnackbarLauncher?
) {
    val cardClickMessage = stringResource(id = R.string.custom_focus_indicators_example_7_message)
    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
    var isCheck by remember { mutableStateOf(false) }

    OutlinedCard(
        onClick = {
            snackbarLauncher?.show(cardClickMessage)
        },
        modifier = Modifier
            .padding(top = 8.dp)
            .indication(
                interactionSource = interactionSource,
                indication = VisibleFocusIndication(
                    themedIndicationColor = MaterialTheme.colorScheme.primary
                )
            ).toggleable(
                value = isCheck,
                onValueChange = { isCheck = it },
                role = Role.Checkbox,
                interactionSource = interactionSource,
                indication = ripple()
            )
        ,
        // Key technique: Provide a common interactionSource to both clickable Card and indication.
        interactionSource = interactionSource
    ) {
        Row (
            horizontalArrangement = Arrangement.Center,
            modifier = Modifier.padding(horizontal = 12.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            BodyText(
                textId = R.string.custom_focus_indicators_example_7_card_description,
                modifier = Modifier
            )
            Checkbox(checked = isCheck, onCheckedChange = null)
        }
    }
}

Before Tap: enter image description here

After Click enter: enter image description here

Expected: After the click event, it should keep showing the Border indication. An additional issue is if there is some other update, say, a progress bar update, in those cases, it also loses the focus from the element.

I've used some boilerplate code. Any idea how this can be solved?

Upvotes: 0

Views: 26

Answers (1)

Amit Kundu
Amit Kundu

Reputation: 119

I have found the issue. The issue was I was creating an instance of the Focusable class. Without remember scope. So the class was rerendering resulting is loosing focus.

Upvotes: 0

Related Questions