ComposableでTimePickerDialogを作る

## Compose の Material3 には DatePickeDialog はあるのに TimePickerDialog が無い

Compose の Material3 には DatePickeDialog はあるのに TimePickerDialog は用意されていないようです。という事でもろもろ参考にしながら作ってみました。

### プレビュー

こんな感じの見た目になります。

![スクリーンショット 2024-01-22 053256.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20596/d4ded438-0b33-c3cb-6750-6a461d82a742.png)

### ComposeMaterial3 の導入
まずは、ComposeMaterial3 を入れます。
```app/buid.bradle.kts
implementation("androidx.compose.material3:material3:1.1.2")
```
### ダイアログ本体

続いてダイアログ本体を作ります。
現時点では、TimePicke 自体が Experimental 扱いのようなので、@OptIn アノテーションを付けています。
今回は AlertDialog にしましたが Dialog でも良いと思います。

```CommonTimePickeDialog.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CommonTimePickerDialog(
    timePickerState: TimePickerState = TimePickerState(0, 0, true),
    onDismiss: () -> Unit = {},
    onConfirm: (state: TimePickerState) -> Unit = {},
) {
    AlertDialog(
        onDismissRequest = onDismiss,
        properties = DialogProperties(
            usePlatformDefaultWidth = false
        ),
    ) {
        Surface(
            shape = MaterialTheme.shapes.extraLarge,
            tonalElevation = 6.dp,
            modifier = Modifier
                .width(IntrinsicSize.Min)
                .height(IntrinsicSize.Min)
                .background(
                    shape = MaterialTheme.shapes.extraLarge,
                    color = MaterialTheme.colorScheme.surface
                ),
        ) {
            Column(
                modifier = Modifier.padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(bottom = 20.dp),
                    text = stringResource(id = R.string.label_select_time),
                    style = MaterialTheme.typography.labelMedium
                )
                TimePicker(state = timePickerState)
                Row(
                    modifier = Modifier
                        .height(40.dp)
                        .fillMaxWidth()
                ) {
                    Spacer(modifier = Modifier.weight(1f))
                    Button(onClick = { onConfirm(timePickerState) }) {
                        Text(stringResource(id = R.string.label_ok))
                    }
                }
            }
        }
    }
}
```
### 呼び出し側

最後に呼び出し側を作ります。
今回はテキストフィールドをタップするとダイアログが開くようにしました。
こちらも、@OptIn アノテーションを付けています。

```TimeSelector.kt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimeSelector(
    initialHour: Int = 0,
    initialMinutes: Int = 0,
    timeValue: String = "",
    onTimeSelect: (TimePickerState) -> Unit = {},
) {
    val showTimePicker = remember {
        mutableStateOf(false)
    }
    TextField(
        modifier = Modifier
            .clickable {
                showTimePicker.value = true
            }
            .width(100.dp),
        textStyle = TextStyle.Default.copy(fontSize = 20.sp),
        enabled = false,
        value = timeValue,
        onValueChange = { }
    )
    if (showTimePicker.value) {
        CommonTimePickerDialog(
            // OKを押さずに閉じた場合は初期値に戻る方が自然だと思うのでここで初期化しています。
            timePickerState = remember {
                TimePickerState(
                    initialHour = initialHour,
                    initialMinute = initialMinutes,
                    is24Hour = true
                )
            },
            onConfirm = {
                onTimeSelect(it)
                showTimePicker.value = false
            },
            onDismiss = {
                showTimePicker.value = false
            }
        )
    }
}
```