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.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import com.example.medilightv2android.AppScreen
|
import com.example.medilightv2android.AppScreen
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -46,6 +47,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
var isMeasuring by remember { mutableStateOf(false) }
|
var isMeasuring by remember { mutableStateOf(false) }
|
||||||
var showCatheterizeConfirm by remember { mutableStateOf(false) }
|
var showCatheterizeConfirm by remember { mutableStateOf(false) }
|
||||||
var showCatheterizeSheet 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 showUrineCamera by remember { mutableStateOf(false) }
|
||||||
var showAddCatheterDialog by remember { mutableStateOf(false) }
|
var showAddCatheterDialog by remember { mutableStateOf(false) }
|
||||||
var showDisconnectionBanner by remember { mutableStateOf(false) }
|
var showDisconnectionBanner by remember { mutableStateOf(false) }
|
||||||
@@ -132,26 +135,118 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 카메라 요청 대기 상태
|
// 배뇨 기록 입력 방식 선택 다이얼로그
|
||||||
var pendingCamera by remember { mutableStateOf(false) }
|
if (showCatheterizeSheet) {
|
||||||
LaunchedEffect(pendingCamera) {
|
AlertDialog(
|
||||||
if (pendingCamera) {
|
onDismissRequest = { showCatheterizeSheet = false },
|
||||||
pendingCamera = false
|
title = { Text("배뇨 기록", fontWeight = FontWeight.Bold) },
|
||||||
showUrineCamera = true
|
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) {
|
if (showManualInput) {
|
||||||
CatheterizeSheet(
|
var volumeText by remember { mutableStateOf("") }
|
||||||
onDismiss = { showCatheterizeSheet = false },
|
AlertDialog(
|
||||||
onCameraRequested = {
|
onDismissRequest = { showManualInput = false },
|
||||||
showCatheterizeSheet = false
|
title = { Text("소변량 입력") },
|
||||||
pendingCamera = true
|
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 = {
|
confirmButton = {
|
||||||
appState.resetLevel()
|
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) {
|
val grouped = remember(records) {
|
||||||
records.groupBy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it.timestamp)) }
|
records.groupBy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it.timestamp)) }
|
||||||
|
.toSortedMap(compareByDescending { it }) // 최신 날짜가 위
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
Reference in New Issue
Block a user