From a601926ad71a891698d8cbaa47a31afeadda6ad4 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Tue, 29 Sep 2026 17:06:25 +0900 Subject: [PATCH] =?UTF-8?q?ui(hospital):=20=EC=A0=95=EB=A0=AC=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20labdb=20=EB=B0=94=EB=A1=9C=20=EC=95=84?= =?UTF-8?q?=EB=9E=98=EB=A1=9C=20=EC=98=AC=EB=A6=AC=EA=B3=A0,=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=EB=8F=84=20?= =?UTF-8?q?=EC=9E=90=EC=84=B8=EB=A5=BC=20=EA=B3=A0=EB=A5=B4=EA=B2=8C=20?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 접는 구간에 넣어 봤더니 정렬은 환자마다 한 번은 반드시 펴야 해서 불편했다(2026-09-29 오후 피드백). 프로브를 붙이는 순서가 곧 화면 순서다: 연결 → labdb → 정렬 → 환자명 → 자세 → 기울기 → 채움 정도 → 측정 시작/중지 → 채널 접촉 정렬이 자세보다 위로 가면 자세를 안 고른 채 정렬에 들어오는 것이 정상 경로가 된다. 그래서 정렬 화면의 기울기 카드 아래에 자세 칩을 둔다 — AppState.clinicalPosture 공유라 어느 쪽에서 골라도 같은 값이고, Sitting 35°~55° 띠와 BV 의 supine 분기가 거기서 뜬다. 측정·좌우 스캔 중에는 바꾸지 못한다. 접는 구간 A 는 "측정 조건"(반복 횟수·복부 두께)만 남는다. 측정 중 한 줄 요약에 부착 위치를 넣는다. A34 실기: 병원 화면 순서 확인, 정렬 화면에서 Sitting 을 고르면 카드에 띠가 뜬다. Co-Authored-By: Claude Fable 5.1 --- .../java/com/medithings/vesiscan/AppState.kt | 3 +- .../ui/views/clinical/AnchorAlignView.kt | 38 +++++++++--- .../ui/views/clinical/HospitalModeView.kt | 61 ++++++++++--------- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/medithings/vesiscan/AppState.kt b/app/src/main/java/com/medithings/vesiscan/AppState.kt index cd29536..94a2266 100644 --- a/app/src/main/java/com/medithings/vesiscan/AppState.kt +++ b/app/src/main/java/com/medithings/vesiscan/AppState.kt @@ -63,7 +63,8 @@ class AppState(private val context: Context) { * 들고 있으면 같은 프로브·같은 자리에서 **정렬 BV 와 임상 BV 가 다른 식으로** * 계산된다 — 실제로 정렬이 `supine` 을 안 넘겨 그렇게 돌고 있었다(2026-09-10). * - * 정렬 화면에는 자세를 고르는 칸이 없다. 병원 모드에서 고른 값을 그대로 쓴다. + * 정렬 화면과 병원 모드 어느 쪽에서든 고를 수 있다(2026-09-29 정렬 화면에 칩 추가 — + * 병원 화면에서 정렬이 자세보다 위에 있어 자세 없이 들어오는 것이 정상 경로다). */ var clinicalPosture: com.medithings.vesiscan.models.ClinicalPosture by mutableStateOf(com.medithings.vesiscan.models.ClinicalPosture.SUPINE) diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt index ed282bc..222e1a4 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt @@ -36,6 +36,7 @@ package com.medithings.vesiscan.ui.views.clinical import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -604,20 +605,41 @@ fun AnchorAlignView(appState: AppState) { } Spacer(Modifier.height(8.dp)) - // 프로브 기울기 — 상시. 자세는 이 화면에서 고르지 않는다(AppState.clinicalPosture - // 참조). 병원 화면에서 고른 값으로 요구 범위를 보이되, 어느 자세 기준인지 적는다 — - // 안 적으면 Supine 으로 들어와 띠가 없을 때 "왜 안 뜨나"가 된다. + // 프로브 기울기 — 상시. 요구 범위(Sitting 35°~55°)는 바로 아래 자세를 본다. ImuTiltCard( tiltDeg = liveTilt, posture = appState.clinicalPosture, connected = isConnected, running = measuring || lrRunning, ) - Spacer(Modifier.height(4.dp)) - Text( - "자세 ${appState.clinicalPosture.label} 기준 — 병원 임상 측정 화면에서 고른 값", - fontSize = 11.sp, color = MlSecondaryText, - ) + Spacer(Modifier.height(10.dp)) + + // 자세 — 여기서도 고른다(2026-09-29). 병원 화면에서 정렬 카드가 자세 칩보다 위에 + // 있어(연결 → labdb → 정렬) 자세를 안 고른 채 들어오는 것이 정상 경로다. 값은 + // AppState 공유라 돌아가면 그대로다. 위 카드의 요구 범위와 BV 의 supine 분기가 + // 이 값을 본다. 측정·좌우 스캔 중에는 바꾸지 못한다 — 그 위치 데이터의 조건이다. + Text("자세", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText) + Row( + Modifier.fillMaxWidth().padding(top = 6.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + ClinicalPosture.entries.forEach { p -> + val on = p == appState.clinicalPosture + Box( + Modifier + .weight(1f) + .background(if (on) MlPrimary else Color.White, RoundedCornerShape(10.dp)) + .clickable(enabled = !measuring && !lrRunning) { appState.clinicalPosture = p } + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text( + p.label, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, + color = if (on) Color.White else MlSecondaryText, + ) + } + } + } Spacer(Modifier.height(12.dp)) Text( diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt index 02f5904..8882f8b 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt @@ -17,12 +17,16 @@ * 측정하는 동안 실제로 보는 것은 넷이다 — **기울기 · 채움 정도 · 채널 접촉 · 시작/중지**. * 이 넷이 스크롤 없이 한 화면에 있어야 한다. 그래서 위에는 그 흐름만 둔다: * - * 연결 → labdb → 환자명 → 자세 → 기울기 → 채움 정도 → 측정 시작/중지 → 채널 접촉(측정 중) + * 연결 → labdb → 정렬 → 환자명 → 자세 → 기울기 → 채움 정도 → 측정 시작/중지 → 채널 접촉(측정 중) * - * 세션에 한 번 만지거나 안 만지는 것은 아래 접는 구간([Collapsible])이다 — 측정 조건· - * 부착 위치(정렬·반복·두께), 회차 상세(용적·파형), labdb 업로드·기록, 수동 확인(프로브 - * 파라미터 · 1회/연속 BV). 접힌 머리줄에 요약을 적어 미정렬·미업로드는 펴 보지 않아도 - * 보인다. 수동 확인 값은 저장도 업로드도 되지 않는다 — 한 흐름으로 붙여 두면 조작자가 + * 정렬은 위에 둔다(2026-09-29 오후) — 접는 구간에 넣어 봤더니 환자마다 한 번은 반드시 + * 펴야 해서 불편했다. 프로브를 붙이는 순서가 곧 화면 순서다: 연결하고, 붙일 자리를 + * 찾고, 그 다음이 환자·자세·측정이다. 자세는 정렬 화면에서도 고를 수 있으므로 + * (AppState.clinicalPosture 공유) 정렬이 자세보다 위에 있어도 요구 각도 띠는 거기서 뜬다. + * + * 세션에 한 번 만지거나 안 만지는 것은 아래 접는 구간([Collapsible])이다 — 측정 조건 + * (반복·두께), 회차 상세(용적·파형), labdb 업로드·기록, 수동 확인(프로브 파라미터 · + * 1회/연속 BV). 접힌 머리줄에 요약을 적어 미업로드는 펴 보지 않아도 보인다. 수동 확인 값은 저장도 업로드도 되지 않는다 — 한 흐름으로 붙여 두면 조작자가 * 수동 측정을 프로토콜의 한 단계로 착각하므로 제목에 못 박고 접어 둔다. */ package com.medithings.vesiscan.ui.views.clinical @@ -497,10 +501,15 @@ fun HospitalModeView(appState: AppState) { // ── 화면 구성 (2026-09-29 임상 참관 뒤) ───────────────────────── // 측정하는 동안 실제로 보는 것은 넷이다 — 기울기 · 채움 정도 · 채널 접촉 · // 시작/중지. 이 넷이 스크롤 없이 한 화면에 있어야 한다. 그래서 위에는 그 흐름 - // (연결 → labdb → 환자 → 자세 → 기울기 → 채움 → 시작 → 접촉)만 두고, 세션에 - // 한 번 만지거나 안 만지는 것(정렬·반복·두께, 회차 상세, 업로드, 수동 도구)은 + // (연결 → labdb → 정렬 → 환자 → 자세 → 기울기 → 채움 → 시작 → 접촉)만 두고, + // 세션에 한 번 만지거나 안 만지는 것(반복·두께, 회차 상세, 업로드, 수동 도구)은 // 아래 [Collapsible] 로 내렸다. 접힌 머리줄에 요약을 적어 펴 보지 않아도 - // 상태(미정렬·미업로드)가 보이게 한다. + // 상태(미업로드)가 보이게 한다. + + val anchorSummary = appState.anchorCm?.let { cm -> + if (appState.anchorBasis?.isAlgorithm == true) "부착 위치 ${cm}cm 확정" + else "부착 위치 ${cm}cm · ${appState.anchorBasis?.label ?: "근거 불명"}" + } ?: "부착 위치 미정렬" // 2. labdb 등록 상태. **측정 전에** 보여야 한다 — 미등록이면 업로드 시도조차 // 일어나지 않는데(ClinicalSessionStore·HospitalLabdbUploader 가 둘 다 @@ -510,7 +519,7 @@ fun HospitalModeView(appState: AppState) { // 진행·중지·접촉이 스크롤 없이 한 화면에 들어오게 한다 — 이 화면을 다시 짠 이유다. if (running) { Text( - "환자 ${patient.ifBlank { "(이름 없음)" }} · ${posture.label} · labdb " + + "환자 ${patient.ifBlank { "(이름 없음)" }} · ${posture.label} · $anchorSummary · labdb " + (if (LabdbCredentials.isRegistered) "연결됨" else "미등록"), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MlNeutral, modifier = Modifier.fillMaxWidth() @@ -522,7 +531,17 @@ fun HospitalModeView(appState: AppState) { LabdbStatusCard(enabled = true) Spacer(Modifier.height(12.dp)) - // 3. 환자명 + // 3. 부착 위치 정렬 — 연결 여부로 막지 않는다(2026-09-28). 정렬 화면에 + // [기기 연결] 이 있고 측정 버튼은 거기서 연결을 본다. 들어가서 붙이면 된다. + AnchorCard( + anchorCm = appState.anchorCm, + basis = appState.anchorBasis, + enabled = true, + onAlign = { appState.currentScreen = AppScreen.ANCHOR_ALIGN }, + ) + Spacer(Modifier.height(12.dp)) + + // 4. 환자명 OutlinedTextField( value = patient, onValueChange = { @@ -642,27 +661,11 @@ fun HospitalModeView(appState: AppState) { // ── 접는 구간 ─────────────────────────────────────────────────── - // A. 측정 조건 · 부착 위치 — 환자마다 한 번. 머리줄에 정렬 상태를 적어 미정렬이 - // 접힌 채로도 보이게 한다(기록에 부착 위치가 남지 않는 상태를 놓치면 안 된다). - val anchorSummary = appState.anchorCm?.let { cm -> - if (appState.anchorBasis?.isAlgorithm == true) "부착 위치 ${cm}cm 확정" - else "부착 위치 ${cm}cm · ${appState.anchorBasis?.label ?: "근거 불명"}" - } ?: "부착 위치 미정렬" + // A. 측정 조건 — 반복 횟수·복부 두께. 세션에 한 번 정하면 안 건드린다. Collapsible( - title = "측정 조건 · 부착 위치", - summary = "$anchorSummary · ${repeats}회 × ${thickness.combos.size}조합 · 복부 ${thickness.label}", - warn = appState.anchorCm == null, + title = "측정 조건", + summary = "${repeats}회 × ${thickness.combos.size}조합 · 복부 ${thickness.label}", ) { - // 연결 여부로 막지 않는다(2026-09-28). 정렬 화면에 [기기 연결] 이 있고 - // 측정 버튼은 거기서 연결을 본다. 들어가서 붙이면 된다. - AnchorCard( - anchorCm = appState.anchorCm, - basis = appState.anchorBasis, - enabled = !running, - onAlign = { appState.currentScreen = AppScreen.ANCHOR_ALIGN }, - ) - Spacer(Modifier.height(12.dp)) - OutlinedTextField( value = repeatsText, onValueChange = { if (!running) repeatsText = it.filter { c -> c.isDigit() }.take(3) },