Skip to content

Instantly share code, notes, and snippets.

@eevajonnapanula
Created April 30, 2026 03:51
Show Gist options
  • Select an option

  • Save eevajonnapanula/338dda22fdff6cd372bb627edaaa5c87 to your computer and use it in GitHub Desktop.

Select an option

Save eevajonnapanula/338dda22fdff6cd372bb627edaaa5c87 to your computer and use it in GitHub Desktop.
import androidx.compose.foundation.IndicationNodeFactory
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.eevajonna.accessibilitytests.ui.theme.AccessibilityTestsTheme
import kotlinx.coroutines.launch
@Composable
fun FocusScreen() {
Scaffold { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.padding(8.dp)
) {
ButtonWrapper()
SwitchWrapper()
}
}
}
@Composable
private fun ButtonWrapper() {
val buttonInteractionSource = remember { MutableInteractionSource() }
Button(
modifier = Modifier.focusIndication(buttonInteractionSource),
interactionSource = buttonInteractionSource,
onClick = {}
) {
Text("A Button")
}
}
@Composable
private fun SwitchWrapper() {
val switchInteractionSource = remember { MutableInteractionSource() }
var selected by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.focusIndication(switchInteractionSource)
.toggleable(
value = selected,
onValueChange = {
selected = !selected
},
role = Role.Switch,
indication = ripple(),
interactionSource = switchInteractionSource
)
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "A switch"
)
Switch(
checked = selected,
onCheckedChange = {
selected = !selected
},
modifier = Modifier
.clearAndSetSemantics {}
.focusProperties {
canFocus = false
},
interactionSource = switchInteractionSource,
)
}
}
@Composable
@Preview
fun FocusScreenPreview() {
AccessibilityTestsTheme {
FocusScreen()
}
}
class FocusNode(
val interactionSource: InteractionSource,
val color: Color
) : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode {
private var isFocused by mutableStateOf(false)
override fun onAttach() {
coroutineScope.launch {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is FocusInteraction.Focus -> isFocused = true
is FocusInteraction.Unfocus -> isFocused = false
}
}
}
}
override fun ContentDrawScope.draw() {
drawContent()
val inputMode = currentValueOf(LocalInputModeManager).inputMode
if (isFocused && inputMode == InputMode.Keyboard) {
drawRect(
color = color,
topLeft = Offset(
x = 0f,
y = size.height
),
size = Size(
width = size.width,
height = 12f
)
)
}
}
}
private data class FocusIndication(
val color: Color
) : IndicationNodeFactory {
override fun create(interactionSource: InteractionSource): Modifier.Node {
return FocusNode(interactionSource, color)
}
}
@Composable
fun Modifier.focusIndication(
interactionSource: MutableInteractionSource
): Modifier {
val focusColor = MaterialTheme.colorScheme.surfaceTint
val indication = remember {
FocusIndication(
color = focusColor
)
}
return indication(interactionSource, indication)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment