I'm developing a WearOS application, and for a specific piece of data I decided — I'm toying with the idea — that the most compact display would be a matrix of dots.
Each dot can be: OFF (almost black, but not totally), ON (vintage green phosphor) and BLINKING (swinging between ON and OFF).
I did it like this:
internal enum class SegmentStatus { OFF, ON, BLINKING }
internal val segment = Modifier.padding(.2f.dp).width(4.dp).height(6.dp).clip(RoundedCornerShape(2.dp))
internal val mapSegmentStateToModifier = mapOf(
OFF to segment.background(Color.DarkGray),
ON to segment.background(Color.Green),
BLINKING to segment.background(Color.White),
)
@Composable
@Preview
fun PreviewDotMapDisplay() {
val matrix = listOf(
listOf(ON, ON, ON, ON),
listOf(ON, ON, ON, OFF),
listOf(OFF, OFF, OFF, OFF),
listOf(ON, ON, ON, OFF),
listOf(ON, ON, ON, BLINKING),
listOf(ON, OFF, OFF, OFF),
listOf(OFF, OFF, OFF, OFF),
)
Column(modifier = Modifier.background(Color.Black)) {
matrix.forEach { row ->
Row {
row.map { mapSegmentStateToModifier[it] ?: Modifier }
.forEach { Spacer(modifier = it) }
}
}
}
}
Output:
How to make that white "dot" blink? I have no idea
It can be a simple blinking, like fully ON then fully OFF. From there I think I can modify it to add some whistles (ramping up and phosphor overshot, then ramping down and ghosting).
Apart from that main question, I'd like also to hear from you about that code above.
Is it too much of a burden on a smart watch to compose that many Columns
and Rows
and Spacer
? The alternative would be to draw those shapes.
— consider that I expect at most 10 lines and 5 columns
Also, that elvis operator in row.map { mapSegmentStateToModifier[it] ?: Modifier }
annoys me a little, because I know that the else branch is never taken. But kotlin doesn't. Is it possible to create a mapping guaranteed to be exhaustive (with syntax error on constructor if not)?
I did it with:
val offColor = Color.DarkGray
val onColor = Color.Green
val blinkColor = rememberInfiniteTransition("blinking cursor").animateColor(
initialValue = offColor,
targetValue = onColor,
animationSpec = infiniteRepeatable(
animation = tween(500),
repeatMode = RepeatMode.Reverse
),
label = "blinking cursor"
).value
Then I scoped mapSegmentStateToModifier
to the function (instead of the global scope, because rememberInfiniteTransition
cannot be called outside @Composable
):
val mapSegmentStateToModifier = mapOf(
OFF to segment.background(offColor),
ON to segment.background(onColor),
BLINKING to segment.background(blinkColor),
)
It did the trick. Now I'll look forward to the other whistles.