ux: Sensor Alignment 레이아웃 정형화 — 박스 항상 자리, weight 5등분

요구사항 (사용자 정의 순서):
  Title / spacer / Hint1 / spacer / Illustration / spacer /
  Step / spacer / Hint2 / spacer / Start / Skip

변경:
  1) Hint 1 박스: if(waitingForStart) 외부 가드 제거 → 박스는
     항상 렌더. 텍스트만 waitingForStart 시 표시. heightIn min
     96dp (현재 자연 높이 ~64dp 의 1.5배).
  2) Hint 2 박스: 동일 패턴 — 박스 항상 렌더, 텍스트는
     hint2Visible 시만, heightIn min 102dp (28sp 텍스트 기준 1.5배).
  3) Step ↔ Hint 2 간격: Spacer 8dp → weight 1f (5번째 분할).
  4) Skip ↔ 하단: weight 1f → 고정 16dp (사용자 요구 — Skip 이
     화면 아래 가깝게).

결과: 빈 공간이 5개 위치 (Title↔Hint1, Hint1↔Illustration,
      Illustration↔Step, Step↔Hint2, Hint2↔Button) 에 균등 분배.
      내용 없을 때도 Hint 박스 자리는 그대로 유지 (배경/높이).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 16:54:11 +09:00
parent deb47645d8
commit b461bfffba
@@ -237,30 +237,31 @@ fun PlacementGuideView(appState: AppState) {
// Title(top bar) ↔ Hint 1 사이 — 첫 weight spacer (위쪽 빈 공간 균등 분배). // Title(top bar) ↔ Hint 1 사이 — 첫 weight spacer (위쪽 빈 공간 균등 분배).
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// ─── (1) Hint 1 — waitingForStart 일 때만 렌더 (빈 칸 자체 숨김) ─── // ─── (1) Hint 1 — 박스는 항상 자리(높이 1.5×), 텍스트는 waitingForStart 시만 ───
if (waitingForStart) { val hint1Bg = Color(0xFFFFF3E0)
val hint1Bg = Color(0xFFFFF3E0) val hint1Text = remember {
val hint1Text = remember { buildAnnotatedString {
buildAnnotatedString { val full = "Place VesiScan above the pubic bone, then press Start"
val full = "Place VesiScan above the pubic bone, then press Start" val key = "pubic bone"
val key = "pubic bone" val idx = full.indexOf(key, ignoreCase = true)
val idx = full.indexOf(key, ignoreCase = true) if (idx >= 0) {
if (idx >= 0) { append(full.substring(0, idx))
append(full.substring(0, idx)) withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.ExtraBold)) {
withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.ExtraBold)) { append(full.substring(idx, idx + key.length))
append(full.substring(idx, idx + key.length)) }
} append(full.substring(idx + key.length))
append(full.substring(idx + key.length)) } else append(full)
} else append(full)
}
} }
Box( }
modifier = Modifier Box(
.fillMaxWidth() modifier = Modifier
.background(hint1Bg.copy(alpha = 0.7f), RoundedCornerShape(16.dp)) .fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 14.dp), .heightIn(min = 96.dp) // 1.5× of current ~64dp natural height
contentAlignment = Alignment.Center // 상하 중앙 정렬 .background(hint1Bg.copy(alpha = 0.7f), RoundedCornerShape(16.dp))
) { .padding(horizontal = 20.dp, vertical = 14.dp),
contentAlignment = Alignment.Center
) {
if (waitingForStart) {
Text( Text(
text = hint1Text, text = hint1Text,
fontSize = 26.sp, fontSize = 26.sp,
@@ -269,7 +270,6 @@ fun PlacementGuideView(appState: AppState) {
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
} }
Spacer(modifier = Modifier.height(8.dp))
} }
// 타이틀은 top bar 로 이동 (← 옆 절대 가운데) — 본문 중복 제거. // 타이틀은 top bar 로 이동 (← 옆 절대 가운데) — 본문 중복 제거.
@@ -532,18 +532,20 @@ fun PlacementGuideView(appState: AppState) {
} }
} }
Spacer(modifier = Modifier.height(8.dp)) // Step ↔ Hint 2 사이 — weight spacer
Spacer(modifier = Modifier.weight(1f))
// ─── (5) Hint 2 — 측정 중 방향 안내 (waitingForStart 또는 빈 텍스트면 칸 자체 숨김) ─── // ─── (5) Hint 2 — 박스는 항상 자리(높이 1.5×), 텍스트는 가시 조건일 때만 ───
val hint2Visible = !waitingForStart && displayHint.text.isNotBlank() val hint2Visible = !waitingForStart && displayHint.text.isNotBlank()
if (hint2Visible) { Box(
Box( modifier = Modifier
modifier = Modifier .fillMaxWidth()
.fillMaxWidth() .heightIn(min = 102.dp) // 1.5× of current ~68dp natural height
.background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(16.dp)) .background(hintBg.copy(alpha = 0.7f), RoundedCornerShape(16.dp))
.padding(horizontal = 20.dp, vertical = 14.dp), .padding(horizontal = 20.dp, vertical = 14.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (hint2Visible) {
Text( Text(
text = displayHint, text = displayHint,
fontSize = 28.sp, fontSize = 28.sp,
@@ -552,7 +554,6 @@ fun PlacementGuideView(appState: AppState) {
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
} }
Spacer(modifier = Modifier.height(8.dp))
} }
// 채널 dot row (개발자 모드에서만 표시) // 채널 dot row (개발자 모드에서만 표시)
@@ -1091,8 +1092,8 @@ fun PlacementGuideView(appState: AppState) {
) )
} }
// 하단 weight spacer — Skip 아래 빈 공간 (세 번째 분할). // 하단 작은 padding 만 (Skip 이 화면 아래 가까이 오도록).
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.height(16.dp))
} // end scrollable Column } // end scrollable Column
// Floating settings overlay (개발자 모드) // Floating settings overlay (개발자 모드)