ux: Sensor Alignment 태블릿 레이아웃 재배치 + 글자 키움 (demo-final)
레이아웃 순서 변경:
기존: 타이틀 → 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 <noreply@anthropic.com>
This commit is contained in:
+89
-73
@@ -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
|
||||
)
|
||||
// 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,
|
||||
text = "Place VesiScan above the pubic bone, then press Start",
|
||||
fontSize = 26.sp, // 태블릿: 더 크게
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White,
|
||||
color = Color.Black,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.background(Color(0xFF7C3AED), RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 6.dp, vertical = 1.dp)
|
||||
.fillMaxWidth()
|
||||
.background(hint1Bg.copy(alpha = 0.7f), RoundedCornerShape(16.dp))
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 타이틀은 top bar 로 이동 (← 옆 절대 가운데) — 본문 중복 제거.
|
||||
|
||||
// ── 가이드 텍스트 (Step 아래, 몸 위) — "pubic bone" 인라인 빨강 ExtraBold 강조 ──
|
||||
@@ -473,18 +420,87 @@ fun PlacementGuideView(appState: AppState) {
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// ── 가이드 hint — 일러스트 *아래* 로 이동 (사용자 피드백) ──
|
||||
// ─── (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 = 22.sp, // 26 → 22 (그림이 메인, hint 보조)
|
||||
fontSize = 28.sp, // 22 → 28 (태블릿)
|
||||
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)
|
||||
.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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user