Fix: use AlertDialog for input selection + direct voice/manual dialogs
Replace ModalBottomSheet-based flow with AlertDialog: - Catheterize → AlertDialog with 3 buttons (수기/음성/카메라) - 수기: AlertDialog with number TextField + save - 음성: AlertDialog with auto-start SpeechRecognizer + auto-save - 카메라: showUrineCamera directly VoidingDiaryView: sort date groups descending (newest first) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+111
-16
@@ -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<String?>(null) }
|
||||
var isListening by remember { mutableStateOf(false) }
|
||||
var speechMgr by remember { mutableStateOf<com.example.medilightv2android.speech.SpeechRecognizerManager?>(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("취소") } }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user