ui(hospital): 정렬 카드를 labdb 바로 아래로 올리고, 정렬 화면에서도 자세를 고르게 한다

접는 구간에 넣어 봤더니 정렬은 환자마다 한 번은 반드시 펴야 해서 불편했다(2026-09-29
오후 피드백). 프로브를 붙이는 순서가 곧 화면 순서다:

  연결 → labdb → 정렬 → 환자명 → 자세 → 기울기 → 채움 정도 → 측정 시작/중지 → 채널 접촉

정렬이 자세보다 위로 가면 자세를 안 고른 채 정렬에 들어오는 것이 정상 경로가 된다.
그래서 정렬 화면의 기울기 카드 아래에 자세 칩을 둔다 — AppState.clinicalPosture 공유라
어느 쪽에서 골라도 같은 값이고, Sitting 35°~55° 띠와 BV 의 supine 분기가 거기서 뜬다.
측정·좌우 스캔 중에는 바꾸지 못한다.

접는 구간 A 는 "측정 조건"(반복 횟수·복부 두께)만 남는다. 측정 중 한 줄 요약에 부착
위치를 넣는다.

A34 실기: 병원 화면 순서 확인, 정렬 화면에서 Sitting 을 고르면 카드에 띠가 뜬다.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 17:06:25 +09:00
parent f35ec8f0dc
commit a601926ad7
3 changed files with 64 additions and 38 deletions
@@ -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)
@@ -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))
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(
"자세 ${appState.clinicalPosture.label} 기준 — 병원 임상 측정 화면에서 고른 값",
fontSize = 11.sp, color = MlSecondaryText,
p.label, fontSize = 14.sp, fontWeight = FontWeight.SemiBold,
color = if (on) Color.White else MlSecondaryText,
)
}
}
}
Spacer(Modifier.height(12.dp))
Text(
@@ -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) },