JetpackComposeでキーパッドを作ってみた

[とあるアプリ](https://x.com/c0demattari/status/1871198020618084522)を作っていく中で、上画像のようなタイマー(Android純正の時計アプリより)を設定するUIが必要になり、下画像のようなキーパッドを実装しました。(本当はiOSによくあるホイールピッカーで実装したかったのですが上手くいかなくて断念)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979616/7d0e69f3-eba3-bd78-57d5-54546d87e808.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979616/72f7059d-c3c8-e281-44b4-2ddfed5d810c.png)
# ソースコード
```kotlin
//タイマー追加ダイアログ
@Composable
fun TimerAddingDialog(
    onDismissRequest: () -> Unit,
    vm: MainScreenViewModel
) {
    var timerState by remember { mutableStateOf("000000") } // "hhmmss"形式

    //数字ボタンを押した時の処理
    fun updateTimer(input: String) {
        //先頭が"0"でない場合は追加しない
        if (timerState.first() == '0') {
            if(input == "00") { //00が押されたときの分岐
                if (timerState.getOrNull(1) != '0') {
                    //先頭から2番目も"0"だった場合の処理
                    timerState = (timerState + "0").takeLast(6) //0を1つだけ追加
                    return
                } else {
                    //先頭から2番目が"0"ではない場合の処理
                    timerState = (timerState + input).takeLast(6)
                    return
                }
            }
            timerState = (timerState + input).takeLast(6) //6桁の左詰めで更新
        }
    }

    //バックスペースボタンの処理
    fun removeLastDigit() {
        timerState = timerState.dropLast(1).padStart(6, '0') //最後の桁を削除し、0で埋める
    }

    AlertDialog(
        onDismissRequest = onDismissRequest,
        title = { Text("タイマーの追加") },
        text = {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center,
            ) {
                //タイマー表示
                Text(
                    text = "${timerState.substring(0, 2)}h " +
                            "${timerState.substring(2, 4)}m " +
                            "${timerState.substring(4, 6)}s",
                    modifier = Modifier.padding(8.dp),
                    fontSize = 42.sp
                )

                //数字入力ボタン
                listOf(
                    listOf("1", "2", "3"),
                    listOf("4", "5", "6"),
                    listOf("7", "8", "9"),
                    listOf("00", "0", "icon")
                ).forEach { row ->
                    Row( //数字ボタンを並べる
                        horizontalArrangement = Arrangement.Center,
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        row.forEach { item ->
                            when (item) {
                                "icon" -> OutlinedIconButton(
                                    onClick = { removeLastDigit() },
                                    modifier = Modifier.size(70.dp),
                                ) {
                                    Icon( //バックスペースボタン
                                        painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.baseline_backspace_24)),
                                        contentDescription = "backspace"
                                    )
                                }
                                "00" -> NumpadButton(item, textPadding = 0.dp) {
                                    updateTimer(item)
                                }
                                else -> NumpadButton(item) {
                                    updateTimer(item)
                                }
                            }
                        }
                    }
                }
            }
        },
        confirmButton = {
            TextButton(onClick = {
                vm.addTimer(timerState)
                vm.updateUserData()
                onDismissRequest()
            }) {
                Text("追加")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismissRequest) {
                Text("キャンセル")
            }
        }
    )
}

//テンキー
@Composable
fun NumpadButton(
    text: String,
    textPadding: Dp = 4.dp,
    onClick: () -> Unit
) {
    OutlinedButton(
        onClick = { onClick() },
        shape = CircleShape, //丸型に
        modifier = Modifier
            .padding(2.dp)
            .size(70.dp),
        colors = ButtonDefaults.outlinedButtonColors( //ボタンの色
            containerColor = Color.Transparent,
            contentColor = Color.Black)
    ) {
        Text(
            text = text,
            modifier = Modifier.padding(textPadding),
            fontSize = 18.sp
        )
    }
}
```
# 実際の動作
実際に動いている様子は以下の通りです↓
![screen-20241227-1054552-_1_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3979616/93e3a6d4-510c-0af9-29de-c0a5eee519be.gif)
# 仕組み
簡単な仕組みとしては、6桁の数列に対して選んだ数字が足される(```timerState = (timerState + input)```)→7桁となった数列から、先頭の数字を除外(```.takeLast(6)```)をすることで、数列の更新を行っています。
00が押された際の分岐処理等にやや苦労しましたが綺麗に動いて良かったです。
文字列に対するメソッドとして```.takeLast```や```.dropLast```などがあることへのありがたみをひしひしと感じました。

是非勝手に使ってやってください