UI: PlacementGuide(Sensor Alignment) 리디자인
빌드 107-121 누적 적용: - 3-stop vertical gradient 배경 (MlTeal 14% / 흰 / MlPrimary 10%) — GREEN 진입 시 단색 톤으로 덮임 - 큰 "Sensor Alignment" 타이틀 38sp ExtraBold 검정 (Maximum Bladder Capacity 톤 통일) - Hint 텍스트 "pubic bone" 인라인 빨강 ExtraBold 강조 (AnnotatedString) - Canvas 방향 화살표 4종 제거 — hint 텍스트로만 방향 안내 - Canvas 치골 아치 라인+halo+endpoint 제거 — pubic bone 강조는 hint 인라인 1곳에만 - 실루엣 chestY 16% UP shift (0.06 → 0.09, 비례 유지) - Body 상단 Spacer 8dp Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+44
-52
@@ -30,8 +30,11 @@ import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.medilightv2android.AppState
|
||||
@@ -146,10 +149,18 @@ fun PlacementGuideView(appState: AppState) {
|
||||
animationSpec = androidx.compose.animation.core.tween(600), label = "bg"
|
||||
)
|
||||
|
||||
// 3-stop vertical gradient — MlTeal 14% / 흰 / MlPrimary 10% (GREEN 진입 시 단색 톤으로 덮임)
|
||||
val gradientBrush = Brush.verticalGradient(
|
||||
0f to MlTeal.copy(alpha = 0.14f),
|
||||
0.5f to Color.White,
|
||||
1f to MlPrimary.copy(alpha = 0.10f)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(bgColor)
|
||||
.background(if (isGreenZone) Brush.verticalGradient(listOf(bgColor, bgColor)) else gradientBrush)
|
||||
) {
|
||||
// Top bar
|
||||
Row(
|
||||
@@ -218,12 +229,22 @@ fun PlacementGuideView(appState: AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 가이드 텍스트 (Step 아래, 몸 위) ──
|
||||
// ── 큰 타이틀 "Sensor Alignment" 38sp ExtraBold 검정 (Maximum Bladder Capacity 톤 통일) ──
|
||||
Text(
|
||||
text = "Sensor Alignment",
|
||||
fontSize = 38.sp,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
color = Color.Black,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
// ── 가이드 텍스트 (Step 아래, 몸 위) — "pubic bone" 인라인 빨강 ExtraBold 강조 ──
|
||||
val hintGreen = isGreenZone
|
||||
val hintBg = if (hintGreen) Color(0xFF4CAF50).copy(alpha = 0.12f) else Color(0xFFFFF3E0)
|
||||
val hintFg = if (hintGreen) Color(0xFF4CAF50) else Color(0xFFFF9800)
|
||||
val dots = ".".repeat((scanCount % 3) + 1)
|
||||
val displayHint = buildString {
|
||||
val rawHint = buildString {
|
||||
val hint = directionHint.ifEmpty { " " }
|
||||
append(hint)
|
||||
if (hint.startsWith("Slide") || hint.startsWith("Optimizing") ||
|
||||
@@ -234,6 +255,19 @@ fun PlacementGuideView(appState: AppState) {
|
||||
append(" (CV=${"%.3f".format(lastCv)})")
|
||||
}
|
||||
}
|
||||
val displayHint = buildAnnotatedString {
|
||||
val key = "pubic bone"
|
||||
val idx = rawHint.indexOf(key, ignoreCase = true)
|
||||
if (idx >= 0) {
|
||||
append(rawHint.substring(0, idx))
|
||||
withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.ExtraBold)) {
|
||||
append(rawHint.substring(idx, idx + key.length))
|
||||
}
|
||||
append(rawHint.substring(idx + key.length))
|
||||
} else {
|
||||
append(rawHint)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = displayHint,
|
||||
fontSize = 28.sp,
|
||||
@@ -246,6 +280,9 @@ fun PlacementGuideView(appState: AppState) {
|
||||
.padding(horizontal = 20.dp, vertical = 18.dp)
|
||||
)
|
||||
|
||||
// 큰 타이틀과 몸 사이 간격 (Body 상단 Spacer 32→8dp)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// ── 하복부 Canvas 실루엣 + 센서 + 방향 가이드 ──
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -280,7 +317,8 @@ fun PlacementGuideView(appState: AppState) {
|
||||
val navelInk = Color(0xFF5A3D26).copy(alpha = bodyAlpha)
|
||||
|
||||
// ── Anatomical landmarks (vertical %) ──
|
||||
val chestY = h * 0.06f
|
||||
// 빌드 120-121: 실루엣 Y값 16% UP shift, 비례 유지 (25→9% 또는 6→9% 누적)
|
||||
val chestY = h * 0.09f
|
||||
val waistY = h * 0.42f
|
||||
val hipY = h * 0.76f
|
||||
val pubisY = h * 0.96f
|
||||
@@ -332,17 +370,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
drawCircle(skinShade.copy(alpha = 0.4f * bodyAlpha), 5.5f, Offset(cx, navelY),
|
||||
style = Stroke(width = 0.8f))
|
||||
|
||||
// ── Pubic arch (치골 아치) ──
|
||||
val archY = pubisY - h * 0.04f
|
||||
val archPath = Path().apply {
|
||||
moveTo(cx - hipW * 0.30f, archY)
|
||||
cubicTo(
|
||||
cx - hipW * 0.10f, archY + h * 0.025f,
|
||||
cx + hipW * 0.10f, archY + h * 0.025f,
|
||||
cx + hipW * 0.30f, archY)
|
||||
}
|
||||
drawPath(archPath, skinShade.copy(alpha = 0.45f * bodyAlpha),
|
||||
style = Stroke(width = 1.4f))
|
||||
// 빌드 111: Pubic arch 라인 + halo + endpoint 제거 — pubic bone 강조는 hint 인라인 1곳에만
|
||||
|
||||
// ── 방광 타원 (배꼽~치골 사이) ──
|
||||
val bladderCy = (navelY + pubisY) / 2f
|
||||
@@ -361,7 +389,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
style = Stroke(width = 1.5f, pathEffect = PathEffect.dashPathEffect(floatArrayOf(6f, 4f)))
|
||||
)
|
||||
|
||||
// ── Sensor device (Canvas 안에서 drawImage — oval 중심에 정렬) ──
|
||||
// ── Sensor device (Canvas drawImage — oval 중심에 정렬) ── [Charles 833c110]
|
||||
// 너비를 bladder oval 직경 × 1.15 로 묶어 화면 비율 무관하게 비례.
|
||||
val sensorTargetW = (bladderRx * 2f) * 1.15f
|
||||
val sensorAspect = sensorBitmap.height.toFloat() / sensorBitmap.width.toFloat()
|
||||
@@ -381,43 +409,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
alpha = 0.95f,
|
||||
)
|
||||
|
||||
// ── 방향 화살표 (센서/타원 바깥, 충분한 간격) ──
|
||||
if (!greenZone) {
|
||||
val arrowColor = Color(0xFFFF5722)
|
||||
val arrowLen = 70f
|
||||
val arrowHead = 28f
|
||||
val sw = 9f
|
||||
// 센서 영역 밖으로 확실히 벗어나는 간격
|
||||
val sensorHalfH = h * 0.08f // 센서 이미지 대략 높이의 절반
|
||||
|
||||
when {
|
||||
directionIcon.contains("up") -> {
|
||||
// 타원 위 + 센서 위 확실히 벗어남
|
||||
val ay = bladderCy - bladderRy - sensorHalfH - 20f
|
||||
drawLine(arrowColor, Offset(cx, ay + arrowLen), Offset(cx, ay), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(cx - arrowHead, ay + arrowHead), Offset(cx, ay), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(cx + arrowHead, ay + arrowHead), Offset(cx, ay), strokeWidth = sw)
|
||||
}
|
||||
directionIcon.contains("down") -> {
|
||||
val ay = bladderCy + bladderRy + sensorHalfH + 20f
|
||||
drawLine(arrowColor, Offset(cx, ay - arrowLen), Offset(cx, ay), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(cx - arrowHead, ay - arrowHead), Offset(cx, ay), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(cx + arrowHead, ay - arrowHead), Offset(cx, ay), strokeWidth = sw)
|
||||
}
|
||||
directionIcon.contains("left") -> {
|
||||
val ax = cx - bladderRx - 30f
|
||||
drawLine(arrowColor, Offset(ax + arrowLen, bladderCy), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(ax + arrowHead, bladderCy - arrowHead), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(ax + arrowHead, bladderCy + arrowHead), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
}
|
||||
directionIcon.contains("right") -> {
|
||||
val ax = cx + bladderRx + 30f
|
||||
drawLine(arrowColor, Offset(ax - arrowLen, bladderCy), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(ax - arrowHead, bladderCy - arrowHead), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
drawLine(arrowColor, Offset(ax - arrowHead, bladderCy + arrowHead), Offset(ax, bladderCy), strokeWidth = sw)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 빌드 111(e0f92eb): Canvas 방향 화살표 4종 제거 — hint 텍스트로만 방향 안내
|
||||
}
|
||||
|
||||
// (sensor 이미지는 위 Canvas drawImage 로 이동 — body silhouette과 동일 좌표계)
|
||||
|
||||
Reference in New Issue
Block a user