From c1533a342265c5f3abe3a00187c144b5ef856393 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Wed, 17 Jun 2026 09:44:21 +0900 Subject: [PATCH] =?UTF-8?q?ux:=20Sensor=20Alignment=20=ED=83=9C=EB=B8=94?= =?UTF-8?q?=EB=A6=BF=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20+=20=EA=B8=80=EC=9E=90=20=ED=82=A4?= =?UTF-8?q?=EC=9B=80=20(demo-final)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 레이아웃 순서 변경: 기존: 타이틀 → Step → 일러스트 → Hint 변경: 타이틀 → Hint1 → 일러스트 → Step → Hint2 → 버튼 → Skip Hint 두 영역 분리: Hint 1 (일러스트 위) — 시작 전 안내 "Place VesiScan above the pubic bone, then press Start" waitingForStart=true 일 때만 표시. Hint 2 (일러스트 아래) — 측정 중 방향 안내 "Slide up slightly ↑", "Centered, optimizing..." 등. waitingForStart=false 일 때만 표시. 태블릿 기준 글자 크기: 타이틀 32 → 56sp (ExtraBold) — 화면폭 ≈ 60% 차지 Hint1 신규 26sp Bold Hint2 22 → 28sp Bold Step 22 → 28sp + 3-dot indicator 12/8 → 16/10dp SWEEP/IMU 배지 10 → 13/12sp Skip 15 → 20sp 기존 버튼 ("Start Alignment" 28sp, "Start Scanning" 28sp) 그대로 유지. 영향: - 일반 사용자 (demo flavor): 태블릿 시연 화면 정렬 강화 - dev/feature 브랜치: 동일 패턴 cherry-pick 가능 (별개 commit) Co-Authored-By: Claude Opus 4.7 --- .../ui/views/monitoring/PlacementGuideView.kt | 182 ++++++++++-------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt index 20677ff..4e42e00 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt @@ -199,10 +199,10 @@ fun PlacementGuideView(appState: AppState) { } } } - // (B) 타이틀 — Box.Center로 화면 절대 가운데 (← 폭과 무관). 26→32sp 강조. + // (B) 타이틀 — 태블릿 기준 화면폭 ≈ 60% 차지 (32 → 56sp). Text( text = "Sensor Alignment", - fontSize = 32.sp, + fontSize = 56.sp, fontWeight = FontWeight.ExtraBold, color = Color.Black ) @@ -221,76 +221,23 @@ fun PlacementGuideView(appState: AppState) { .padding(horizontal = 24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // Step indicator + mode badge (이미지 위에 배치) - // Step progress — 텍스트 + 3-dot indicator (현재 단계 강조) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp), - modifier = Modifier.padding(vertical = 6.dp) - ) { - val currentStepIdx = when (placementPhase) { - PlacementPhase.VERTICAL -> 0 - PlacementPhase.LATERAL -> 1 - PlacementPhase.GREEN -> 2 - } + // ─── (1) Hint 1 — 시작 전 안내 ("pubic bone 위에 놓으세요") ─── + // waitingForStart 일 때만 표시. 시작 후엔 빈 공간. + if (waitingForStart) { + val hint1Bg = Color(0xFFFFF3E0) Text( - text = "Step ${currentStepIdx + 1}/3", - fontSize = 22.sp, fontWeight = FontWeight.Bold, color = Color.Black + text = "Place VesiScan above the pubic bone, then press Start", + fontSize = 26.sp, // 태블릿: 더 크게 + fontWeight = FontWeight.Bold, + color = Color.Black, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .background(hint1Bg.copy(alpha = 0.7f), RoundedCornerShape(16.dp)) + .padding(horizontal = 20.dp, vertical = 14.dp) ) - // 3-dot indicator: 완료(○ → ●), 현재(큰 ●), 미완료(○) - Row(horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically) { - repeat(3) { i -> - val isDone = i < currentStepIdx - val isCurrent = i == currentStepIdx - val dotSize = if (isCurrent) 12.dp else 8.dp - val dotColor = when { - isCurrent -> MlPrimary - isDone -> MlPrimary.copy(alpha = 0.6f) - else -> Color.Gray.copy(alpha = 0.3f) - } - Box( - modifier = Modifier - .size(dotSize) - .clip(CircleShape) - .background(dotColor) - ) - } - } - if (isSweepMode) { - Text( - "SWEEP", - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - modifier = Modifier - .background(Color(0xFF7C3AED), RoundedCornerShape(4.dp)) - .padding(horizontal = 6.dp, vertical = 1.dp) - ) - } - // IMU 정보 (Dev 모드 한정) — 자세 + 흔들림 RMS - if (appState.isDevMode && lastImuSamples.isNotEmpty()) { - val posture = remember(lastImuSamples) { - com.example.medilightv2android.managers.ImuAnalysis.estimatePosture(lastImuSamples) - } - val shake = remember(lastImuSamples) { - com.example.medilightv2android.managers.ImuAnalysis.shakeRmsDps(lastImuSamples) - } - val tilt = remember(lastImuSamples) { - com.example.medilightv2android.managers.ImuAnalysis.tiltDegrees(lastImuSamples) - } - Text( - "IMU: $posture · tilt=${"%.0f".format(tilt)}° · shake=${"%.1f".format(shake)}dps", - fontSize = 10.sp, - fontWeight = FontWeight.SemiBold, - color = if (shake > 30f) Color.Red else MlSecondaryText, - modifier = Modifier - .background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp)) - .padding(horizontal = 6.dp, vertical = 1.dp) - ) - } + Spacer(modifier = Modifier.height(8.dp)) } - // 타이틀은 top bar 로 이동 (← 옆 절대 가운데) — 본문 중복 제거. // ── 가이드 텍스트 (Step 아래, 몸 위) — "pubic bone" 인라인 빨강 ExtraBold 강조 ── @@ -473,18 +420,87 @@ fun PlacementGuideView(appState: AppState) { Spacer(modifier = Modifier.height(8.dp)) - // ── 가이드 hint — 일러스트 *아래* 로 이동 (사용자 피드백) ── - Text( - text = displayHint, - fontSize = 22.sp, // 26 → 22 (그림이 메인, hint 보조) - fontWeight = FontWeight.Bold, - color = Color.Black, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(14.dp)) - .padding(horizontal = 16.dp, vertical = 10.dp) - ) + // ─── (3) Step indicator — 일러스트 아래로 이동 ─── + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(vertical = 4.dp) + ) { + val currentStepIdx = when (placementPhase) { + PlacementPhase.VERTICAL -> 0 + PlacementPhase.LATERAL -> 1 + PlacementPhase.GREEN -> 2 + } + Text( + text = "Step ${currentStepIdx + 1}/3", + fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.Black // 22 → 28 + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically) { + repeat(3) { i -> + val isDone = i < currentStepIdx + val isCurrent = i == currentStepIdx + val dotSize = if (isCurrent) 16.dp else 10.dp // 12/8 → 16/10 + val dotColor = when { + isCurrent -> MlPrimary + isDone -> MlPrimary.copy(alpha = 0.6f) + else -> Color.Gray.copy(alpha = 0.3f) + } + Box( + modifier = Modifier + .size(dotSize) + .clip(CircleShape) + .background(dotColor) + ) + } + } + if (isSweepMode) { + Text( + "SWEEP", + fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White, + modifier = Modifier + .background(Color(0xFF7C3AED), RoundedCornerShape(4.dp)) + .padding(horizontal = 8.dp, vertical = 2.dp) + ) + } + if (appState.isDevMode && lastImuSamples.isNotEmpty()) { + val posture = remember(lastImuSamples) { + com.example.medilightv2android.managers.ImuAnalysis.estimatePosture(lastImuSamples) + } + val shake = remember(lastImuSamples) { + com.example.medilightv2android.managers.ImuAnalysis.shakeRmsDps(lastImuSamples) + } + val tilt = remember(lastImuSamples) { + com.example.medilightv2android.managers.ImuAnalysis.tiltDegrees(lastImuSamples) + } + Text( + "IMU: $posture · tilt=${"%.0f".format(tilt)}° · shake=${"%.1f".format(shake)}dps", + fontSize = 12.sp, fontWeight = FontWeight.SemiBold, + color = if (shake > 30f) Color.Red else MlSecondaryText, + modifier = Modifier + .background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp)) + .padding(horizontal = 8.dp, vertical = 2.dp) + ) + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // ─── (5) Hint 2 — 측정 중 방향 안내 ("Slide up slightly ↑" 등) ─── + // waitingForStart 가 아닐 때 (=scanning 또는 phaseHold) 만 표시. + if (!waitingForStart) { + Text( + text = displayHint, + fontSize = 28.sp, // 22 → 28 (태블릿) + fontWeight = FontWeight.Bold, + color = Color.Black, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(16.dp)) + .padding(horizontal = 20.dp, vertical = 14.dp) + ) + } Spacer(modifier = Modifier.height(8.dp)) @@ -940,12 +956,12 @@ fun PlacementGuideView(appState: AppState) { Text("Start Scanning", fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.White) } - // Skip button + // Skip button (태블릿: 15 → 20sp) Spacer(modifier = Modifier.height(8.dp)) TextButton(onClick = { isContinuousMode = false; appState.placementComplete() }) { Text( text = "Skip", - fontSize = 15.sp, + fontSize = 20.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText )