diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index 837153b..4f6958b 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -3,6 +3,7 @@ package com.example.medilightv2android.ui.views.monitoring import android.os.Handler import android.os.Looper import com.example.medilightv2android.AppScreen +import androidx.compose.material3.OutlinedTextField import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -46,6 +47,8 @@ fun PiezoMonitoringView(appState: AppState) { var isMeasuring by remember { mutableStateOf(false) } var showCatheterizeConfirm by remember { mutableStateOf(false) } var showCatheterizeSheet by remember { mutableStateOf(false) } + var showManualInput by remember { mutableStateOf(false) } + var showVoiceInput by remember { mutableStateOf(false) } var showUrineCamera by remember { mutableStateOf(false) } var showAddCatheterDialog by remember { mutableStateOf(false) } var showDisconnectionBanner by remember { mutableStateOf(false) } @@ -132,26 +135,118 @@ fun PiezoMonitoringView(appState: AppState) { ) } - // 카메라 요청 대기 상태 - var pendingCamera by remember { mutableStateOf(false) } - LaunchedEffect(pendingCamera) { - if (pendingCamera) { - pendingCamera = false - showUrineCamera = true - } + // 배뇨 기록 입력 방식 선택 다이얼로그 + if (showCatheterizeSheet) { + AlertDialog( + onDismissRequest = { showCatheterizeSheet = false }, + title = { Text("배뇨 기록", fontWeight = FontWeight.Bold) }, + text = { Text("입력 방식을 선택하세요") }, + confirmButton = {}, + dismissButton = { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + TextButton(onClick = { + showCatheterizeSheet = false + showManualInput = true + }) { Text("수기") } + TextButton(onClick = { + showCatheterizeSheet = false + showVoiceInput = true + }) { Text("음성") } + TextButton(onClick = { + showCatheterizeSheet = false + showUrineCamera = true + }) { Text("카메라") } + } + } + ) } - // 배뇨 기록 시트 (수기/음성/카메라) - if (showCatheterizeSheet) { - CatheterizeSheet( - onDismiss = { showCatheterizeSheet = false }, - onCameraRequested = { - showCatheterizeSheet = false - pendingCamera = true + // 수기 입력 다이얼로그 + if (showManualInput) { + var volumeText by remember { mutableStateOf("") } + AlertDialog( + onDismissRequest = { showManualInput = false }, + title = { Text("소변량 입력") }, + text = { + androidx.compose.foundation.layout.Row(verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = volumeText, + onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) }, + placeholder = { Text("예: 200") }, + keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number), + singleLine = true, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + Text("ml", fontWeight = FontWeight.SemiBold) + } }, - onCatheterized = { - appState.resetLevel() + confirmButton = { + TextButton(onClick = { + val vol = volumeText.toIntOrNull() + if (vol != null && vol in 1..1000) { + com.example.medilightv2android.measure.VoidingRecordStore.add( + context, com.example.medilightv2android.measure.VoidingRecord(volume = vol, method = "manual") + ) + appState.resetLevel() + android.widget.Toast.makeText(context, "배뇨일지에 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show() + showManualInput = false + } + }) { Text("저장") } + }, + dismissButton = { TextButton(onClick = { showManualInput = false }) { Text("취소") } } + ) + } + + // 음성 입력 + if (showVoiceInput) { + var voiceResult by remember { mutableStateOf(null) } + var isListening by remember { mutableStateOf(false) } + var speechMgr by remember { mutableStateOf(null) } + + DisposableEffect(Unit) { onDispose { speechMgr?.destroy() } } + + // 즉시 시작 + LaunchedEffect(Unit) { + if (androidx.core.content.ContextCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO) + == android.content.pm.PackageManager.PERMISSION_GRANTED) { + isListening = true + speechMgr = com.example.medilightv2android.speech.SpeechRecognizerManager( + context = context, + onResult = { candidates -> + isListening = false + val parsed = com.example.medilightv2android.speech.VolumeParser.parseBest(candidates) + if (parsed != null) { + com.example.medilightv2android.measure.VoidingRecordStore.add( + context, com.example.medilightv2android.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice") + ) + appState.resetLevel() + android.widget.Toast.makeText(context, "${parsed.volumeMl} ml 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show() + showVoiceInput = false + } else { + voiceResult = "인식 실패: ${candidates.firstOrNull() ?: ""}" + } + }, + onError = { code -> isListening = false; voiceResult = "오류 ($code)" }, + onReady = { voiceResult = "말씀하세요..." } + ) + speechMgr?.startListening() } + } + + AlertDialog( + onDismissRequest = { speechMgr?.destroy(); showVoiceInput = false }, + title = { Text(if (isListening) "듣는 중..." else "음성 입력") }, + text = { Text(voiceResult ?: "마이크 권한을 확인하세요") }, + confirmButton = { + if (!isListening && voiceResult != null) { + TextButton(onClick = { + isListening = true; voiceResult = null + speechMgr?.startListening() + }) { Text("다시 시도") } + } + }, + dismissButton = { TextButton(onClick = { speechMgr?.destroy(); showVoiceInput = false }) { Text("취소") } } ) } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt index db49417..b040784 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt @@ -34,6 +34,7 @@ fun VoidingDiaryView(onBack: () -> Unit) { val grouped = remember(records) { records.groupBy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it.timestamp)) } + .toSortedMap(compareByDescending { it }) // 최신 날짜가 위 } Scaffold(