UI #2: AutoScan Interval → Scan Interval + SEC/MIN 단위 토글

회의 피드백:
  - 제목 'Auto Scan Interval' → 'Scan Interval'
  - 한글 설명 → '측정 간격 조절'
  - ms 단위 표시 → sec 기본 + min 토글
  - 최소 600ms 고정 (펌웨어 응답 시간 + 안전 마진)

구현:
  - SEC 모드: 0.6 ~ 5.0 s (0.2s step) — 짧은 간격, 실시간
  - MIN 모드: 1 ~ 10 min (30s step)  — 장시간 측정, 배터리 절약
  - 토글 버튼(SEC/MIN) 활성 시 진입 모드의 최소값으로 reset
  - 슬라이더/스테퍼/표시값 모두 모드에 맞춰 동적 변환
  - 기본 모드 추론: 저장된 ms < 60000이면 SEC, 아니면 MIN

PiezoMonitoring in-monitoring settings 슬라이더는 sec-only로 그대로 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 17:55:18 +09:00
parent e7076ed2a0
commit 6c3b88cf2d
@@ -27,14 +27,19 @@ import com.example.medilightv2android.ui.theme.*
import kotlin.math.roundToInt
/**
* Auto Scan Interval 설정 화면.
* Scan Interval 설정 화면.
*
* 흐름: PiezoPersonalization(MaxCapacity + CatheterThreshold)
* → 본 화면 (Auto Scan Interval)
* → 본 화면 (Scan Interval)
* → PlacementGuide → PiezoMonitoring
*
* 600~5000ms 범위, 200ms 단위 snap. PiezoMonitoring의 in-monitoring settings에서
* 같은 슬라이더로 측정 중에도 조정 가능.
* 단위 토글 (SEC/MIN):
* - SEC 모드: 0.6 ~ 5.0 s (0.2s step) — 짧은 간격, 실시간 모니터링
* - MIN 모드: 1 ~ 10 min (30s step) — 장시간 측정, 배터리 절약
* - 모드 전환 시 최소값(SEC=0.6s / MIN=1min)으로 reset
* 최소 600ms 고정 (펌웨어 응답 시간 + 안전 마진).
*
* PiezoMonitoring의 in-monitoring settings는 SEC 슬라이더만 유지 (회의 결정).
*/
@Composable
fun AutoScanIntervalSetupView(appState: AppState) {
@@ -105,6 +110,11 @@ fun AutoScanIntervalSetupView(appState: AppState) {
@Composable
private fun AutoScanIntervalCard(appState: AppState) {
// 단위 모드 — 현재 저장된 ms 값으로 기본 모드 추론 (60000ms 미만 = SEC)
var unitMode by remember {
mutableStateOf(if (appState.piezoSettings.autoScanIntervalMs < 60000) "sec" else "min")
}
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
@@ -114,45 +124,74 @@ private fun AutoScanIntervalCard(appState: AppState) {
modifier = Modifier.padding(horizontal = 20.dp, vertical = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Auto Scan Interval", fontSize = 34.sp, fontWeight = FontWeight.ExtraBold)
Text("Scan Interval", fontSize = 34.sp, fontWeight = FontWeight.ExtraBold)
Spacer(modifier = Modifier.height(8.dp))
Text("Auto scan 사이클 간격 — 짧을수록 빠른 갱신, 길수록 안정",
Text("측정 간격 조절",
fontSize = 13.sp, color = MlSecondaryText)
Spacer(modifier = Modifier.height(20.dp))
Spacer(modifier = Modifier.height(16.dp))
// SEC / MIN 토글
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
UnitToggleButton(label = "SEC", selected = unitMode == "sec") {
if (unitMode != "sec") {
unitMode = "sec"
appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = 600)
}
}
UnitToggleButton(label = "MIN", selected = unitMode == "min") {
if (unitMode != "min") {
unitMode = "min"
appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = 60000)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// 현재 값 표시 (모드에 따라 sec/min 변환)
val currentMs = appState.piezoSettings.autoScanIntervalMs
Row(verticalAlignment = Alignment.Bottom) {
Text("${appState.piezoSettings.autoScanIntervalMs}", fontSize = 64.sp,
val displayValue = if (unitMode == "sec") "%.1f".format(currentMs / 1000.0)
else "%.1f".format(currentMs / 60000.0)
Text(displayValue, fontSize = 64.sp,
fontWeight = FontWeight.ExtraBold, color = MlPrimary)
Spacer(modifier = Modifier.width(8.dp))
Text("ms", fontSize = 24.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText,
Text(if (unitMode == "sec") "s" else "min",
fontSize = 24.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText,
modifier = Modifier.padding(bottom = 12.dp))
}
Spacer(modifier = Modifier.height(12.dp))
// 스테퍼 — 모드별 step 크기
val stepMs = if (unitMode == "sec") 200 else 30_000
val minMs = if (unitMode == "sec") 600 else 60_000
val maxMs = if (unitMode == "sec") 5_000 else 600_000
Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) {
StepperButton(icon = Icons.Default.Remove) {
val next = (appState.piezoSettings.autoScanIntervalMs - 200).coerceIn(600, 5000)
val next = (currentMs - stepMs).coerceIn(minMs, maxMs)
appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next)
}
StepperButton(icon = Icons.Default.Add) {
val next = (appState.piezoSettings.autoScanIntervalMs + 200).coerceIn(600, 5000)
val next = (currentMs + stepMs).coerceIn(minMs, maxMs)
appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next)
}
}
Spacer(modifier = Modifier.height(12.dp))
// 슬라이더 — 모드별 범위/snap
val stepCount = ((maxMs - minMs) / stepMs) - 1 // Slider steps는 양 끝점 사이 점 개수
Slider(
value = appState.piezoSettings.autoScanIntervalMs.toFloat(),
value = currentMs.toFloat(),
onValueChange = {
val snapped = ((it / 200f).roundToInt() * 200).coerceIn(600, 5000)
val snapped = ((it / stepMs).roundToInt() * stepMs).coerceIn(minMs, maxMs)
appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = snapped)
},
valueRange = 600f..5000f,
steps = 21,
valueRange = minMs.toFloat()..maxMs.toFloat(),
steps = stepCount.coerceAtLeast(0),
modifier = Modifier.padding(horizontal = 24.dp),
colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)
)
@@ -160,6 +199,21 @@ private fun AutoScanIntervalCard(appState: AppState) {
}
}
@Composable
private fun UnitToggleButton(label: String, selected: Boolean, onClick: () -> Unit) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(if (selected) MlPrimary else Color.Gray.copy(alpha = 0.15f))
.clickable(onClick = onClick)
.padding(horizontal = 24.dp, vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold,
color = if (selected) Color.White else Color.Black)
}
}
@Composable
private fun StepperButton(icon: androidx.compose.ui.graphics.vector.ImageVector, onClick: () -> Unit) {
Box(