UI: 앱 아이콘 교체 + Placement 힌트 텍스트 정리

- 앱 아이콘: 방광이.png로 교체 (webp→png, adaptive-icon 제거)
- Placement: "Centering" → "Checking", "LR balanced. Final check" → "Final check"
- 모든 방향 힌트에 화살표(↑↓←→) 추가 — phaseProtected 시 아이콘 추론 보장
- 디바운스 중에도 directionIcon은 최신 guideResult로 갱신 (화살표 누락 수정)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:21:10 +09:00
parent 2cfa56a06d
commit ef38aa6eaf
23 changed files with 26 additions and 28 deletions
@@ -226,7 +226,7 @@ fun PlacementGuideView(appState: AppState) {
val hint = directionHint.ifEmpty { " " }
append(hint)
if (hint.startsWith("Slide") || hint.startsWith("Optimizing") ||
hint.startsWith("Centering") || hint.startsWith("Adjust")) {
hint.startsWith("Checking") || hint.startsWith("Adjust")) {
append(dots)
}
if (appState.isDevMode && !lastCv.isNaN()) {
@@ -685,7 +685,17 @@ fun PlacementGuideView(appState: AppState) {
} else if (phaseProtected && phaseHoldHint.isNotEmpty()) {
// 보호 중 → 전환 힌트 강제 유지, lastHintChangeMs 안 건드림
directionHint = phaseHoldHint
directionIcon = "checkmark.circle"
// 힌트 텍스트에서 방향 아이콘 추론 (화살표 유지)
directionIcon = when {
phaseHoldHint.contains("up", ignoreCase = true) || phaseHoldHint.contains("↑") -> "arrow.up"
phaseHoldHint.contains("down", ignoreCase = true) || phaseHoldHint.contains("↓") -> "arrow.down"
phaseHoldHint.contains("left", ignoreCase = true) || phaseHoldHint.contains("←") -> "arrow.left"
phaseHoldHint.contains("right", ignoreCase = true) || phaseHoldHint.contains("→") -> "arrow.right"
else -> "checkmark.circle"
}
} else {
// 디바운스 중이라도 화살표 방향은 최신 상태로 갱신
directionIcon = guideResult.icon
}
// GREEN hold 중이면 힌트 강제 유지
@@ -1234,7 +1244,7 @@ private fun computePlacementGuide(
val detectedCount = detected.count { it }
if (detectedCount == 0) {
return PlacementGuideResult("Slide up", "arrow.up", 0,
return PlacementGuideResult("Slide up ↑", "arrow.up", 0,
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
}
@@ -1292,7 +1302,7 @@ private fun computePlacementGuide(
val lrHint = when {
l4 > 0 && l5 > 0 && l4 > l5 -> "Slide left ←"
l4 > 0 && l5 > 0 && l5 > l4 -> "Slide right →"
else -> "Centering"
else -> "Checking"
}
val lrIcon = when {
l4 > 0 && l5 > 0 && l4 > l5 -> "arrow.left"
@@ -1328,13 +1338,13 @@ private fun computePlacementGuide(
val hint: String; val icon: String
when {
(ch0det || ch1det) && !ch2det && !ch3det -> {
hint = "Slide up"; icon = "arrow.up"
hint = "Slide up ↑"; icon = "arrow.up"
}
(ch3det || ch2det) && !ch0det && !ch1det -> {
hint = "Slide down"; icon = "arrow.down"
hint = "Slide down ↓"; icon = "arrow.down"
}
else -> {
hint = "Slide up"; icon = "arrow.up"
hint = "Slide up ↑"; icon = "arrow.up"
}
}
return PlacementGuideResult(hint, icon, centerDetected * 10,
@@ -1379,7 +1389,7 @@ private fun computePlacementGuide(
val ch5ok = detected.getOrElse(5) { false }
// 항상 LATERAL 단계를 거침 — 전환 시 항상 Checking 표시
val lrHint = "Centering"
val lrHint = "Checking"
val lrIcon = "checkmark.circle"
val score = (40 + ((1.0 - kotlin.math.abs(gradient) / gradientThr) * 20).toInt())
.coerceIn(40, 60)
@@ -1396,16 +1406,16 @@ private fun computePlacementGuide(
val ch4ok = detected.getOrElse(4) { false }
val ch5ok = detected.getOrElse(5) { false }
// 항상 LATERAL 단계를 거침
return PlacementGuideResult("Centering",
return PlacementGuideResult("Checking",
"checkmark.circle", 50,
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.LATERAL)
} else if (gradient > 0) {
val intensity = if (gradient > 0.8) "Slide down" else "Slide down slightly"
val intensity = if (gradient > 0.8) "Slide down ↓" else "Slide down slightly ↓"
return PlacementGuideResult(intensity, "arrow.down",
(20 + centerDetected * 5).coerceAtMost(35),
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
} else {
val intensity = if (gradient < -0.8) "Slide up" else "Slide up slightly"
val intensity = if (gradient < -0.8) "Slide up ↑" else "Slide up slightly ↑"
return PlacementGuideResult(intensity, "arrow.up",
(20 + centerDetected * 5).coerceAtMost(35),
phase = PlacementPhase.VERTICAL, nextPhase = PlacementPhase.VERTICAL)
@@ -1428,7 +1438,7 @@ private fun computePlacementGuide(
// 절대 차이 10 sample 이하 → 좌우 충분히 균등, 바로 GREEN
if (ch4det && ch5det && kotlin.math.abs(len4 - len5) <= 10) {
return PlacementGuideResult(
"LR balanced. Final check...",
"Final check...",
"checkmark.circle", 60,
phase = PlacementPhase.LATERAL, nextPhase = PlacementPhase.GREEN)
}
@@ -1582,10 +1592,10 @@ private fun computePlacementGuide(
adjustHint = "Slide right →"; adjustIcon = "arrow.right"
}
gradient > 0.3 -> {
adjustHint = "Slide down"; adjustIcon = "arrow.down"
adjustHint = "Slide down ↓"; adjustIcon = "arrow.down"
}
gradient < -0.3 -> {
adjustHint = "Slide up"; adjustIcon = "arrow.up"
adjustHint = "Slide up ↑"; adjustIcon = "arrow.up"
}
else -> {
// 방향 애매 + CV 미달 → ch별 len 분석으로 방향 제시
@@ -1596,10 +1606,10 @@ private fun computePlacementGuide(
// 최소 채널이 최대의 60% 미만 → 약한 쪽으로 안내
if (minCh <= 1) {
// ch0/ch1 약함 → 위로
adjustHint = "Slide up slightly"; adjustIcon = "arrow.up"
adjustHint = "Slide up slightly ↑"; adjustIcon = "arrow.up"
} else {
// ch2/ch3 약함 → 아래로
adjustHint = "Slide down slightly"; adjustIcon = "arrow.down"
adjustHint = "Slide down slightly ↓"; adjustIcon = "arrow.down"
}
} else {
adjustHint = "Optimizing"; adjustIcon = "checkmark.circle"
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB