From 502151b967dd93b9cfd0212826ebf8d0904154b7 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Mon, 4 May 2026 17:20:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Spot=203=ED=9A=8C=20median=20+=20Placem?= =?UTF-8?q?ent=20=EA=B0=80=EC=9D=B4=EB=93=9C=20=EB=94=9C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=203=EC=B4=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spot: - 1회 즉시 → 3회 연속 측정(200ms 간격) → median 표시 - 측정 중 로딩 인디케이터 - 2회 이상 유효 시 median 적용 Placement: - 가이드 텍스트 최소 유지시간 4초 → 3초 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../views/monitoring/PiezoMonitoringView.kt | 37 ++++++++++++++++--- .../ui/views/monitoring/PlacementGuideView.kt | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index dab89c0..9c9e7e9 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -3,6 +3,8 @@ package com.example.medilightv2android.ui.views.monitoring import android.os.Handler import android.os.Looper import android.util.Log +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.launch import com.example.medilightv2android.AppScreen import androidx.compose.material3.OutlinedTextField import androidx.compose.foundation.Canvas @@ -817,24 +819,47 @@ fun PiezoMonitoringView(appState: AppState) { } } - // Spot (single measurement, 1.5초 쿨다운) + // Spot (3회 측정 → median) var spotCooldown by remember { mutableStateOf(false) } + var spotInProgress by remember { mutableStateOf(false) } + val spotScope = rememberCoroutineScope() Button( onClick = { - if (!isMeasuring && !isAutoMeasuring && !spotCooldown) { - measure() + if (!isMeasuring && !isAutoMeasuring && !spotCooldown && !spotInProgress) { + spotInProgress = true spotCooldown = true - handler.postDelayed({ spotCooldown = false }, 1500) + spotScope.launch { + val spotResults = mutableListOf() + repeat(3) { + measure() + while (isMeasuring) { kotlinx.coroutines.delay(100) } + if (lastVolumeMl > 0) spotResults.add(lastVolumeMl) + kotlinx.coroutines.delay(200) + } + if (spotResults.size >= 2) { + spotResults.sort() + val median = spotResults[spotResults.size / 2] + lastVolumeMl = median + maxVolumeMl = maxOf(maxVolumeMl, median) + Log.d("PiezoMonitor", "SPOT median=${"%.1f".format(median)}ml from ${spotResults.map { "%.0f".format(it) }}") + val maxVol = appState.piezoSettings.maxVolume.toDouble() + val pct = if (maxVol > 0) (median / maxVol).coerceAtMost(1.0) else 0.0 + val level = (pct * BladderLevel.MAX_LEVEL).toInt().coerceIn(1, BladderLevel.MAX_LEVEL) + appState.bladderLevel = appState.bladderLevel.copy(currentLevel = level) + } + spotInProgress = false + handler.postDelayed({ spotCooldown = false }, 1000) + } } }, - enabled = !isMeasuring && !isAutoMeasuring && !spotCooldown, + enabled = !isMeasuring && !isAutoMeasuring && !spotCooldown && !spotInProgress, modifier = Modifier.weight(1f).height(56.dp), shape = RoundedCornerShape(14.dp), colors = ButtonDefaults.buttonColors( containerColor = urgency.gradientColors.first() ) ) { - if (isMeasuring && !isAutoMeasuring) { + if (spotInProgress) { CircularProgressIndicator(color = Color.White, modifier = Modifier.size(18.dp), strokeWidth = 2.dp) } else { Text("Spot", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = Color.White) 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 47181e1..68da804 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 @@ -545,7 +545,7 @@ fun PlacementGuideView(appState: AppState) { val now = System.currentTimeMillis() val elapsed = now - lastHintChangeMs - if ((debounceCount >= 2 && elapsed >= 4000) || guideResult.isPass) { + if ((debounceCount >= 2 && elapsed >= 3000) || guideResult.isPass) { directionHint = guideResult.hint directionIcon = guideResult.icon placementScore = guideResult.score