From 079774c8697e587547d02d1e95d77cef99a7014d Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 23 Apr 2026 11:29:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Placement=20Guide=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=97=B0=EC=86=8D=20=EC=8A=A4=EC=BA=94=20(3?= =?UTF-8?q?=EC=B4=88=20=EA=B0=84=EA=B2=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수동 1회 → 자동 반복 스캔 (Start/Stop 토글) - 3초마다 maa 전송 → Score 실시간 갱신 - 사용자가 센서 움직이면서 점수 변화를 즉시 확인 가능 - "Scanning every 3s — move sensor and watch the score" 안내 문구 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ui/views/monitoring/PlacementGuideView.kt | 140 ++++++++++-------- 1 file changed, 82 insertions(+), 58 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 f15b01e..555a230 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 @@ -57,6 +57,7 @@ fun PlacementGuideView(appState: AppState) { val analyzer = remember { PiezoEchoAnalyzer.shared } var isScanning by remember { mutableStateOf(false) } + var isContinuousMode by remember { mutableStateOf(false) } val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) } val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) } var directionHint by remember { mutableStateOf("") } @@ -280,75 +281,85 @@ fun PlacementGuideView(appState: AppState) { Spacer(modifier = Modifier.height(24.dp)) - // Check Position button - Button( - onClick = { - if (!isScanning) { - isScanning = true - bleManager.piezoCollector.onMultiChannelComplete = { channels -> - isScanning = false - scanCount++ + // Scan function (reusable) + fun performScan() { + if (isScanning) return + isScanning = true + bleManager.piezoCollector.onMultiChannelComplete = { channels -> + isScanning = false + scanCount++ - val detected = MutableList(6) { false } - val urineLens = MutableList(6) { 0 } + val detected = MutableList(6) { false } + val urineLens = MutableList(6) { 0 } - for (ch in channels) { - if (ch.buffer.isNotEmpty() && ch.channel < 6) { - val analysis = analyzer.analyzeChannel(ch.buffer, ch.channel) - if (analysis.isValid && analysis.result != null) { - detected[ch.channel] = true - urineLens[ch.channel] = analysis.result.urineLen - } else { - val gate = UrinAI.detect(ch.buffer) - if (gate.detected) { - detected[ch.channel] = true - urineLens[ch.channel] = gate.bw - gate.fw - } - } + for (ch in channels) { + if (ch.buffer.isNotEmpty() && ch.channel < 6) { + val analysis = analyzer.analyzeChannel(ch.buffer, ch.channel) + if (analysis.isValid && analysis.result != null) { + detected[ch.channel] = true + urineLens[ch.channel] = analysis.result.urineLen + } else { + val gate = UrinAI.detect(ch.buffer) + if (gate.detected) { + detected[ch.channel] = true + urineLens[ch.channel] = gate.bw - gate.fw } } - - for (i in 0 until 6) { - channelDetected[i] = detected[i] - channelUrineLen[i] = urineLens[i] - } - lastChannelData = channels - - // Direction guidance (urineLen-aware) - val guideResult = computePlacementGuide(detected, urineLens) - directionHint = guideResult.hint - directionIcon = guideResult.icon - placementScore = guideResult.score - - // Lock if score >= 70 or 4+ detected - if (guideResult.score >= 70 || detected.count { it } >= 4) { - isLocked = true - } - - Log.d("PlacementGuide", "Placement: ${detected.count { it }}/6 -- ${ - detected.mapIndexed { idx, d -> if (d) "CH$idx OK" else "CH$idx X" }.joinToString(" ") - }") - - // Save placement log - logService.logPlacement( - channelDetected = detected, - direction = directionHint - ) } - bleManager.piezoCollector.onLog = { msg -> - Log.d("PlacementGuide", msg) - } - bleManager.sendAllChannels() + } + + for (i in 0 until 6) { + channelDetected[i] = detected[i] + channelUrineLen[i] = urineLens[i] + } + lastChannelData = channels + + val guideResult = computePlacementGuide(detected, urineLens) + directionHint = guideResult.hint + directionIcon = guideResult.icon + placementScore = guideResult.score + + if (guideResult.score >= 70 || detected.count { it } >= 4) { + isLocked = true + } + + Log.d("PlacementGuide", "Scan #$scanCount: ${detected.count { it }}/6, score=$placementScore%") + + logService.logPlacement( + channelDetected = detected, + direction = directionHint + ) + } + bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) } + bleManager.sendAllChannels() + } + + // Continuous scan loop (3초 간격) + LaunchedEffect(isContinuousMode) { + if (isContinuousMode) { + performScan() + while (isContinuousMode) { + kotlinx.coroutines.delay(3000) + if (isContinuousMode && !isScanning) performScan() + } + } + } + + // Start/Stop button + Button( + onClick = { + if (isContinuousMode) { + isContinuousMode = false + } else { + isContinuousMode = true } }, - enabled = !isScanning, modifier = Modifier .fillMaxWidth() .height(52.dp), shape = RoundedCornerShape(14.dp), colors = ButtonDefaults.buttonColors( - containerColor = if (isScanning) Color.Gray else MlPrimary, - disabledContainerColor = Color.Gray + containerColor = if (isContinuousMode) Color.Red else MlPrimary ) ) { if (isScanning) { @@ -360,13 +371,26 @@ fun PlacementGuideView(appState: AppState) { Spacer(modifier = Modifier.width(8.dp)) } Text( - text = if (isScanning) "Scanning..." else "Check Position", + text = when { + isContinuousMode -> "Stop Scanning" + scanCount == 0 -> "Start Scanning" + else -> "Resume Scanning" + }, fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = Color.White ) } + if (isContinuousMode) { + Text( + "Scanning every 3s — move sensor and watch the score", + fontSize = 11.sp, color = MlSecondaryText, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + // Start Measurement button (when 4+ channels detected) if (isLocked || detectedCount >= 4) { Spacer(modifier = Modifier.height(12.dp))