From 898fb776057695adda7dd259f8de334e4d0449b8 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Wed, 20 May 2026 16:25:55 +0900 Subject: [PATCH] =?UTF-8?q?UI:=20PlacementGuide(Sensor=20Alignment)=20?= =?UTF-8?q?=EB=A6=AC=EB=94=94=EC=9E=90=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 빌드 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) --- .../ui/views/monitoring/PlacementGuideView.kt | 96 +++++++++---------- 1 file changed, 44 insertions(+), 52 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 253ed22..78ef234 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 @@ -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과 동일 좌표계)