ux: Sensor Alignment — Step 0/N, arrow↔hint sync, arrow 위치, oval 색

1) 시작 전 (waitingForStart) Step 표기:
   "Step 1/3" → "Step 0/3" 으로 표시, 점/체크포인트 row 전체
   숨김 (시작 후에만 점 등장).

2) 화살표 ↔ Hint 2 텍스트 mismatch 수정 (V1 path):
   debounce 중 else 분기에서 directionIcon 을 guideResult.icon
   으로 즉시 갱신 → 텍스트는 "위로 올려" 인데 화살표는 가끔
   아래를 가리키는 깜빡임 발생.
   수정: directionHint 텍스트에서 방향 추론 (phaseHoldHint 와
   동일 로직) → 화면에 보이는 텍스트와 항상 일관.
   V2 path 는 이미 동일 source(st.action) 라 영향 없음.

3) 화살표 위치 — 센서/방광 쪽으로 당김:
   vertCloser = arrowDrawH * 0.45f 도입.
   - 위 화살표 cya: 기존 + vertCloser  (살짝 아래로)
   - 아래 화살표 cya: 기존 - vertCloser (살짝 위로)
   좌/우 화살표는 변경 없음.

4) 방광 oval 색 (센서 주위 테두리) — 미정렬 상태:
   Cyan → Color(0xFFFFB74D) (Material orange 300, warm hint).
   greenZone 진입 시는 기존 Green 4CAF50 유지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 17:25:20 +09:00
parent f2fb561b9a
commit a1d68bd007
@@ -362,7 +362,8 @@ fun PlacementGuideView(appState: AppState) {
val bladderCy = navelY + (pubisY - navelY) * 0.75f
val bladderRx = w * 0.18f
val bladderRy = bladderRx * 0.70f
val ovalColor = if (greenZone) Color(0xFF4CAF50) else Color.Cyan
// In-position 아닐 때는 연한 주황 (warm hint), 완료되면 기존 그린.
val ovalColor = if (greenZone) Color(0xFF4CAF50) else Color(0xFFFFB74D)
drawOval(
color = ovalColor.copy(alpha = 0.12f),
topLeft = Offset(cx - bladderRx, bladderCy - bladderRy),
@@ -403,18 +404,20 @@ fun PlacementGuideView(appState: AppState) {
val arrowDrawH = arrowDrawW * (arrowBitmap.height.toFloat() / arrowBitmap.width.toFloat())
val sensorHalfH = h * 0.08f
val gap = 20f
// 위/아래 화살표를 센서/방광 쪽으로 가깝게 당김 (사용자 요구).
val vertCloser = arrowDrawH * 0.45f
// 화살표 center 위치 + 회전 각도 (icon 이 우향이므로 right=0°)
data class ArrowPlacement(val cxa: Float, val cya: Float, val rot: Float)
val placement: ArrowPlacement? = when {
directionIcon.contains("up") -> ArrowPlacement(
cx,
bladderCy - bladderRy - sensorHalfH - gap - arrowDrawH / 2f,
bladderCy - bladderRy - sensorHalfH - gap - arrowDrawH / 2f + vertCloser,
-90f,
)
directionIcon.contains("down") -> ArrowPlacement(
cx,
bladderCy + bladderRy + sensorHalfH + gap + arrowDrawH / 2f,
bladderCy + bladderRy + sensorHalfH + gap + arrowDrawH / 2f - vertCloser,
90f,
)
directionIcon.contains("left") -> ArrowPlacement(
@@ -482,16 +485,19 @@ fun PlacementGuideView(appState: AppState) {
}
idx to 3
}
// waitingForStart 일 때는 "Step 0/N" + 모든 점 숨김 (체크포인트 포함).
val displayStepIdx = if (waitingForStart) -1 else currentStepIdx
Text(
text = "Step ${currentStepIdx + 1}/$totalSteps",
text = "Step ${displayStepIdx + 1}/$totalSteps",
fontSize = 28.sp, fontWeight = FontWeight.Bold, color = Color.Black
)
if (!waitingForStart) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically) {
repeat(totalSteps) { i ->
val isDone = i < currentStepIdx
val isCurrent = i == currentStepIdx
val dotSize = if (isCurrent) 16.dp else 10.dp // 12/8 → 16/10
val dotSize = if (isCurrent) 16.dp else 10.dp
val dotColor = when {
isCurrent -> MlPrimary
isDone -> MlPrimary.copy(alpha = 0.6f)
@@ -505,6 +511,7 @@ fun PlacementGuideView(appState: AppState) {
)
}
}
}
if (isSweepMode) {
Text(
"SWEEP",
@@ -881,8 +888,15 @@ fun PlacementGuideView(appState: AppState) {
else -> "checkmark.circle"
}
} else {
// 디바운스 중이라도 화살표 방향은 최신 상태로 갱신
directionIcon = guideResult.icon
// 디바운스 중 — 화살표를 hint 텍스트에서 추론해 텍스트와 일관 유지.
// (이전: guideResult.icon 즉시 갱신 → "위로 올려" 메세지에 아래 화살표 깜빡임)
directionIcon = when {
directionHint.contains("up", ignoreCase = true) || directionHint.contains("↑") -> "arrow.up"
directionHint.contains("down", ignoreCase = true) || directionHint.contains("↓") -> "arrow.down"
directionHint.contains("left", ignoreCase = true) || directionHint.contains("←") -> "arrow.left"
directionHint.contains("right", ignoreCase = true) || directionHint.contains("→") -> "arrow.right"
else -> directionIcon // 유지
}
}
// GREEN hold 중이면 힌트 강제 유지