feat(alignment): 센서 detach 시 주황 경고 팝업 — 텍스트 변경만으로 사용자가 놓치던 문제

- DetachmentDetection.isDetached 가 true 가 되는 rising edge 에 Dialog 팝업
- wakeup 알람과 동일한 주황(Material 500) 톤 + Warning 아이콘
- 재부착 시 팝업 자동 dismiss (사용자 확인 부담 ↓)
- 기존 directionHint / LED mode 4 / phase reset 동작은 그대로 유지

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 16:36:20 +09:00
parent 4dc0b1acb2
commit 4e85091b90
@@ -161,6 +161,10 @@ fun PlacementGuideView(appState: AppState) {
onDispose { bleManager.sendLedMode(0) } onDispose { bleManager.sendLedMode(0) }
} }
// Detachment alert (rising edge → 팝업, 재부착 시 자동 해제)
var showDetachAlert by remember { mutableStateOf(false) }
var prevDetached by remember { mutableStateOf(false) }
val detectedCount = channelDetected.count { it } val detectedCount = channelDetected.count { it }
val isGreenZone = isLocked || placementScore >= 70 val isGreenZone = isLocked || placementScore >= 70
@@ -176,6 +180,53 @@ fun PlacementGuideView(appState: AppState) {
1f to MlPrimary.copy(alpha = 0.10f) 1f to MlPrimary.copy(alpha = 0.10f)
) )
// 센서 분리 알림 (주황 warning) — wakeup 알람과 동일 패턴
if (showDetachAlert) {
androidx.compose.ui.window.Dialog(
onDismissRequest = { },
properties = androidx.compose.ui.window.DialogProperties(
dismissOnBackPress = false, dismissOnClickOutside = false
)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.92f)
.clip(RoundedCornerShape(24.dp))
.background(Color(0xFFFF9800)) // Material Orange 500
.padding(horizontal = 28.dp, vertical = 36.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(Icons.Default.Warning, contentDescription = null,
tint = Color.White, modifier = Modifier.size(56.dp))
Spacer(modifier = Modifier.height(12.dp))
Text("센서가 분리되었습니다", fontSize = 26.sp,
fontWeight = FontWeight.ExtraBold, color = Color.White,
textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(6.dp))
Text("Sensor detached", fontSize = 16.sp,
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(8.dp))
Text("치골 위쪽에 다시 위치시켜 주세요",
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f),
textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { showDetachAlert = false },
colors = ButtonDefaults.buttonColors(
containerColor = Color.White, contentColor = Color(0xFFFF9800)
),
modifier = Modifier.fillMaxWidth().height(56.dp),
shape = RoundedCornerShape(14.dp)
) {
Text("확인", fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
}
}
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -756,6 +807,10 @@ fun PlacementGuideView(appState: AppState) {
val fullCh = channels.filter { it.buffer.size >= 20 } val fullCh = channels.filter { it.buffer.size >= 20 }
val buffers = channels.sortedBy { it.channel }.map { it.buffer } val buffers = channels.sortedBy { it.channel }.map { it.buffer }
val detachResult = if (fullCh.size >= 6) DetachmentDetection.detect(buffers) else null val detachResult = if (fullCh.size >= 6) DetachmentDetection.detect(buffers) else null
val nowDetached = detachResult?.isDetached == true
if (nowDetached && !prevDetached) showDetachAlert = true // rising edge → 팝업
if (!nowDetached && prevDetached) showDetachAlert = false // 재부착 → 자동 dismiss
prevDetached = nowDetached
if (detachResult != null && detachResult.isDetached) { if (detachResult != null && detachResult.isDetached) {
directionHint = "Sensor detached. Restart above pubic bone." directionHint = "Sensor detached. Restart above pubic bone."