feat: Placement Guide 실시간 연속 스캔 (3초 간격)
- 수동 1회 → 자동 반복 스캔 (Start/Stop 토글) - 3초마다 maa 전송 → Score 실시간 갱신 - 사용자가 센서 움직이면서 점수 변화를 즉시 확인 가능 - "Scanning every 3s — move sensor and watch the score" 안내 문구 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+41
-17
@@ -57,6 +57,7 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
val analyzer = remember { PiezoEchoAnalyzer.shared }
|
val analyzer = remember { PiezoEchoAnalyzer.shared }
|
||||||
|
|
||||||
var isScanning by remember { mutableStateOf(false) }
|
var isScanning by remember { mutableStateOf(false) }
|
||||||
|
var isContinuousMode by remember { mutableStateOf(false) }
|
||||||
val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) }
|
val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||||
val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) }
|
val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) }
|
||||||
var directionHint by remember { mutableStateOf("") }
|
var directionHint by remember { mutableStateOf("") }
|
||||||
@@ -280,10 +281,9 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
// Check Position button
|
// Scan function (reusable)
|
||||||
Button(
|
fun performScan() {
|
||||||
onClick = {
|
if (isScanning) return
|
||||||
if (!isScanning) {
|
|
||||||
isScanning = true
|
isScanning = true
|
||||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||||
isScanning = false
|
isScanning = false
|
||||||
@@ -314,41 +314,52 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
lastChannelData = channels
|
lastChannelData = channels
|
||||||
|
|
||||||
// Direction guidance (urineLen-aware)
|
|
||||||
val guideResult = computePlacementGuide(detected, urineLens)
|
val guideResult = computePlacementGuide(detected, urineLens)
|
||||||
directionHint = guideResult.hint
|
directionHint = guideResult.hint
|
||||||
directionIcon = guideResult.icon
|
directionIcon = guideResult.icon
|
||||||
placementScore = guideResult.score
|
placementScore = guideResult.score
|
||||||
|
|
||||||
// Lock if score >= 70 or 4+ detected
|
|
||||||
if (guideResult.score >= 70 || detected.count { it } >= 4) {
|
if (guideResult.score >= 70 || detected.count { it } >= 4) {
|
||||||
isLocked = true
|
isLocked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("PlacementGuide", "Placement: ${detected.count { it }}/6 -- ${
|
Log.d("PlacementGuide", "Scan #$scanCount: ${detected.count { it }}/6, score=$placementScore%")
|
||||||
detected.mapIndexed { idx, d -> if (d) "CH$idx OK" else "CH$idx X" }.joinToString(" ")
|
|
||||||
}")
|
|
||||||
|
|
||||||
// Save placement log
|
|
||||||
logService.logPlacement(
|
logService.logPlacement(
|
||||||
channelDetected = detected,
|
channelDetected = detected,
|
||||||
direction = directionHint
|
direction = directionHint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
bleManager.piezoCollector.onLog = { msg ->
|
bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) }
|
||||||
Log.d("PlacementGuide", msg)
|
|
||||||
}
|
|
||||||
bleManager.sendAllChannels()
|
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
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(52.dp),
|
.height(52.dp),
|
||||||
shape = RoundedCornerShape(14.dp),
|
shape = RoundedCornerShape(14.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = if (isScanning) Color.Gray else MlPrimary,
|
containerColor = if (isContinuousMode) Color.Red else MlPrimary
|
||||||
disabledContainerColor = Color.Gray
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (isScanning) {
|
if (isScanning) {
|
||||||
@@ -360,13 +371,26 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = if (isScanning) "Scanning..." else "Check Position",
|
text = when {
|
||||||
|
isContinuousMode -> "Stop Scanning"
|
||||||
|
scanCount == 0 -> "Start Scanning"
|
||||||
|
else -> "Resume Scanning"
|
||||||
|
},
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = Color.White
|
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)
|
// Start Measurement button (when 4+ channels detected)
|
||||||
if (isLocked || detectedCount >= 4) {
|
if (isLocked || detectedCount >= 4) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user