From be76f85e34c738b0040a1b33d3c63dad78b7c63f Mon Sep 17 00:00:00 2001 From: jjangddu Date: Wed, 24 Jun 2026 17:54:54 +0900 Subject: [PATCH] =?UTF-8?q?feat(piezo):=20=EB=8F=84=EB=84=9B=20=EC=9C=84?= =?UTF-8?q?=20status=20chip=20=EC=9D=84=20posture=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98+=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - urgency message Text 자리에 자세 chip (아이콘 + 라벨) 배치 - 색·아이콘·라벨이 currentPosture 에 따라 Crossfade 300ms 로 부드럽게 전환 - top bar 의 작은 inline posture Row 제거 (중복 표시 방지) - UNKNOWN 상태 가시성 ↑ : tint #616161 + 라벨 'Detecting' (회색 투명+'—' 대비) - Raw BLE 녹화 버튼은 dev 모드에서만 노출 (legacy 유지) Co-Authored-By: Claude Opus 4.7 --- .../views/monitoring/PiezoMonitoringView.kt | 177 +++++++++--------- 1 file changed, 90 insertions(+), 87 deletions(-) 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 b339a28..4ed1629 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 @@ -1115,68 +1115,40 @@ fun PiezoMonitoringView(appState: AppState) { Spacer(modifier = Modifier.width(8.dp)) - // Posture icon (Phase 1) — flowchart-based 자세 분류 결과 표시. - // SUPINE/SITTING/STANDING/UNKNOWN 에 따라 아이콘+색 변경. - val postureIcon = when (currentPosture) { - com.example.medilightv2android.managers.Posture.LYING -> Icons.Default.Hotel - com.example.medilightv2android.managers.Posture.NON_LYING -> Icons.Default.AccessibilityNew - com.example.medilightv2android.managers.Posture.WALKING -> Icons.Default.DirectionsWalk - com.example.medilightv2android.managers.Posture.UNKNOWN -> Icons.Default.HelpOutline - } - val postureTint = when (currentPosture) { - com.example.medilightv2android.managers.Posture.LYING -> Color(0xFF2196F3) - com.example.medilightv2android.managers.Posture.NON_LYING -> Color(0xFF4CAF50) - com.example.medilightv2android.managers.Posture.WALKING -> Color(0xFFD32F2F) - com.example.medilightv2android.managers.Posture.UNKNOWN -> Color.Gray.copy(alpha = 0.5f) - } - val postureLabel = when (currentPosture) { - com.example.medilightv2android.managers.Posture.LYING -> "Lying" - com.example.medilightv2android.managers.Posture.NON_LYING -> "Upright" - com.example.medilightv2android.managers.Posture.WALKING -> "Walking" - com.example.medilightv2android.managers.Posture.UNKNOWN -> "—" - } - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - postureIcon, contentDescription = postureLabel, - tint = postureTint, modifier = Modifier.size(20.dp), - ) - Spacer(modifier = Modifier.width(3.dp)) - Text(postureLabel, fontSize = (11 * fontScale).sp, - fontWeight = FontWeight.SemiBold, color = postureTint) - } - Spacer(modifier = Modifier.width(8.dp)) - // Raw BLE capture toggle — IMU/packet 디버그용 (Downloads/VesiScan_RawBLE/) - var rawBleCapturing by remember { - mutableStateOf(com.example.medilightv2android.services.RawBleLogger.isCapturing) + // Dev mode 에서만 노출 (legacy 유지) — 일반 사용자에겐 숨김. + if (appState.isDevMode) { + var rawBleCapturing by remember { + mutableStateOf(com.example.medilightv2android.services.RawBleLogger.isCapturing) + } + IconButton( + onClick = { + if (rawBleCapturing) { + val f = com.example.medilightv2android.services.RawBleLogger.stopCapture() + val n = com.example.medilightv2android.services.RawBleLogger.count + rawBleCapturing = false + android.widget.Toast.makeText(context, + "Raw BLE: $n packets saved to ${f?.name}", + android.widget.Toast.LENGTH_LONG).show() + } else { + val f = com.example.medilightv2android.services.RawBleLogger.startCapture() + rawBleCapturing = true + android.widget.Toast.makeText(context, + "Raw BLE capture started → ${f.name}", + android.widget.Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.size(28.dp) + ) { + Icon( + if (rawBleCapturing) Icons.Default.RadioButtonChecked else Icons.Default.FiberManualRecord, + contentDescription = if (rawBleCapturing) "Stop raw BLE capture" else "Start raw BLE capture", + tint = if (rawBleCapturing) Color.Red else Color.Gray.copy(alpha = 0.5f), + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.width(4.dp)) } - IconButton( - onClick = { - if (rawBleCapturing) { - val f = com.example.medilightv2android.services.RawBleLogger.stopCapture() - val n = com.example.medilightv2android.services.RawBleLogger.count - rawBleCapturing = false - android.widget.Toast.makeText(context, - "Raw BLE: $n packets saved to ${f?.name}", - android.widget.Toast.LENGTH_LONG).show() - } else { - val f = com.example.medilightv2android.services.RawBleLogger.startCapture() - rawBleCapturing = true - android.widget.Toast.makeText(context, - "Raw BLE capture started → ${f.name}", - android.widget.Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.size(28.dp) - ) { - Icon( - if (rawBleCapturing) Icons.Default.RadioButtonChecked else Icons.Default.FiberManualRecord, - contentDescription = if (rawBleCapturing) "Stop raw BLE capture" else "Start raw BLE capture", - tint = if (rawBleCapturing) Color.Red else Color.Gray.copy(alpha = 0.5f), - modifier = Modifier.size(20.dp) - ) - } - Spacer(modifier = Modifier.width(4.dp)) // Catheter count (탭하면 추가 팝업) var showAddCatheter by remember { mutableStateOf(false) } @@ -1276,33 +1248,64 @@ fun PiezoMonitoringView(appState: AppState) { Arrangement.spacedBy(nonDevSpacing) ) { - // Status badge — 임상 모드에선 측정 라벨, 일반 모드에선 urgency 메시지. - // 빌드 114: dev 모드 한정 탭 시 더미 측정값 주입 - val clinicalSession = com.example.medilightv2android.services.ClinicalSessionStore.currentSession - val statusText = if (clinicalSession != null) - "${clinicalSession.posture.label} · ${clinicalSession.step.label}" - else urgency.message - val statusColor = urgency.accentColor - Text( - text = statusText, - fontSize = (14 * fontScale).sp, - fontWeight = FontWeight.Bold, - color = statusColor, - modifier = Modifier - .clip(RoundedCornerShape(20.dp)) - .background(statusColor.copy(alpha = 0.15f)) - .then(if (appState.isDevMode) Modifier.clickable { - val maxVol = appState.piezoSettings.maxVolume - val dummy = (60..(maxVol - 50).coerceAtLeast(70)).random().toDouble() - lastVolumeMl = dummy - maxVolumeMl = maxOf(maxVolumeMl, dummy) - displayMaxVolumeMl = maxVolumeMl - val pct = (dummy / maxVol.toDouble()).coerceAtMost(1.0) - val level = (pct * BladderLevel.MAX_LEVEL).toInt().coerceIn(0, BladderLevel.MAX_LEVEL) - appState.bladderLevel = appState.bladderLevel.copy(currentLevel = level) - } else Modifier) - .padding(horizontal = 20.dp, vertical = 8.dp) - ) + // Status chip — 자세 아이콘+텍스트 (urgency message 자리에 배치). + // 빌드 114: dev 모드 한정 탭 시 더미 측정값 주입. + // 색/아이콘/라벨은 currentPosture 에 따라 변하며 Crossfade 300ms 로 부드럽게 전환. + androidx.compose.animation.Crossfade( + targetState = currentPosture, + animationSpec = androidx.compose.animation.core.tween(300), + label = "posture-chip" + ) { posture -> + val icon = when (posture) { + com.example.medilightv2android.managers.Posture.LYING -> Icons.Default.Hotel + com.example.medilightv2android.managers.Posture.NON_LYING -> Icons.Default.AccessibilityNew + com.example.medilightv2android.managers.Posture.WALKING -> Icons.Default.DirectionsWalk + com.example.medilightv2android.managers.Posture.UNKNOWN -> Icons.Default.HelpOutline + } + val tint = when (posture) { + com.example.medilightv2android.managers.Posture.LYING -> Color(0xFF2196F3) + com.example.medilightv2android.managers.Posture.NON_LYING -> Color(0xFF4CAF50) + com.example.medilightv2android.managers.Posture.WALKING -> Color(0xFFD32F2F) + com.example.medilightv2android.managers.Posture.UNKNOWN -> Color(0xFF616161) // 진한 회색 — 가시성 확보 + } + val label = when (posture) { + com.example.medilightv2android.managers.Posture.LYING -> "Lying" + com.example.medilightv2android.managers.Posture.NON_LYING -> "Upright" + com.example.medilightv2android.managers.Posture.WALKING -> "Walking" + com.example.medilightv2android.managers.Posture.UNKNOWN -> "Detecting" + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(20.dp)) + .background(tint.copy(alpha = 0.15f)) + .then(if (appState.isDevMode) Modifier.clickable { + val maxVol = appState.piezoSettings.maxVolume + val dummy = (60..(maxVol - 50).coerceAtLeast(70)).random().toDouble() + lastVolumeMl = dummy + maxVolumeMl = maxOf(maxVolumeMl, dummy) + displayMaxVolumeMl = maxVolumeMl + val pct = (dummy / maxVol.toDouble()).coerceAtMost(1.0) + val lvl = (pct * BladderLevel.MAX_LEVEL).toInt().coerceIn(0, BladderLevel.MAX_LEVEL) + appState.bladderLevel = appState.bladderLevel.copy(currentLevel = lvl) + } else Modifier) + .padding(horizontal = 20.dp, vertical = 8.dp) + ) { + Icon( + icon, + contentDescription = label, + tint = tint, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + label, + fontSize = (16 * fontScale).sp, + fontWeight = FontWeight.Bold, + color = tint + ) + } + } // Donut + Bladdy sizing — 회의 결정: dev/일반 통일. val baseDonutSize = donutFitSize