feat(demo): V2 default + 그래프/문구 일치 + Slider lock (스크롤 충돌 방지)
1. V2 alignment 를 모든 진입점 default 로
- GreenZoneConstants.alignmentAlgo = V2
- PlacementGuideView LaunchedEffect 의 clinical 한정 분기 제거 → 항상 V2
- 사용자 경험 통일 (4-stage Method D)
2. 그래프 ↔ 문구 일치 — 잔여 모순 fix
- CH3_STABILIZE 의 STOP action: 이전엔 isLocked=true + GREEN LED + checkmark
아이콘 → '완료처럼 보이는데 알고리즘은 stabilizing 중' 모순. 변경: phase가
LR_BALANCE 일 때만 finalStop → 진짜 GREEN. CH3_STABILIZE STOP 은 아이콘 X.
- PROBE_LR action: 이전엔 arrow.left (한쪽 화살표). msg 의 ↔ 와 불일치.
변경: 아이콘 없음 (msg 의 ↔ 만으로 양방향 의미 전달).
3. LockableSlider 신규 — 스크롤 충돌 차단
- 잠금 아이콘 1회 탭 → 활성화 → drag 가능 → 다시 탭하면 잠금
- 잠김 상태에서는 slider disabled → 위아래 스크롤 손가락 끌림에 영향 X
- 적용: PiezoMonitoring 의 dev panel 17 sliders +
ReminderSettings 의 2 sliders (배뇨 알림 기간, 기상 알람 기준 시간)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ object GreenZoneConstants {
|
|||||||
@Volatile var detectionMethod: DetectionMethod = DetectionMethod.METHOD_C
|
@Volatile var detectionMethod: DetectionMethod = DetectionMethod.METHOD_C
|
||||||
@Volatile var bvMethod: BvMethod = BvMethod.V41
|
@Volatile var bvMethod: BvMethod = BvMethod.V41
|
||||||
@Volatile var placementGuideMode: PlacementGuideMode = PlacementGuideMode.SIMPLE
|
@Volatile var placementGuideMode: PlacementGuideMode = PlacementGuideMode.SIMPLE
|
||||||
@Volatile var alignmentAlgo: AlignmentAlgo = AlignmentAlgo.V1 // dev 토글 — default V1 (legacy)
|
@Volatile var alignmentAlgo: AlignmentAlgo = AlignmentAlgo.V2 // default V2 (4-stage Method D). V1 은 dev 토글로 fallback.
|
||||||
|
|
||||||
/** Posture pitch 임계값 (deg). |pitch| > 이 값 → LYING, ≤ 이 값 → NON_LYING. dev 모드에서 조정 가능. */
|
/** Posture pitch 임계값 (deg). |pitch| > 이 값 → LYING, ≤ 이 값 → NON_LYING. dev 모드에서 조정 가능. */
|
||||||
@Volatile var pitchLyingThreshold: Float = 25f
|
@Volatile var pitchLyingThreshold: Float = 25f
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* LockableSlider — 표준 Material3 Slider 를 잠금 아이콘으로 감싼 wrapper.
|
||||||
|
*
|
||||||
|
* 사용 의도:
|
||||||
|
* 설정 패널 등 ScrollView 안의 슬라이더가 위아래 스크롤 시 손가락 끌림에 의해
|
||||||
|
* 값이 의도치 않게 바뀌는 문제 차단. 사용자가 잠금 아이콘을 1회 탭하여 활성화한
|
||||||
|
* 뒤에만 슬라이더가 반응.
|
||||||
|
*
|
||||||
|
* 동작:
|
||||||
|
* - 기본 상태: 잠김 (회색 lock 아이콘 + slider disabled)
|
||||||
|
* - 잠금 아이콘 tap → 활성화 (녹색 lock_open 아이콘 + slider enabled)
|
||||||
|
* - 다시 tap → 재잠금
|
||||||
|
* - 잠금 상태에서는 slider drag 무시 → 위아래 스크롤 안전
|
||||||
|
*
|
||||||
|
* UX 톤:
|
||||||
|
* "1회 터치 후 조정" 패턴 — 의도된 조작만 허용.
|
||||||
|
*/
|
||||||
|
package com.example.medilightv2android.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Lock
|
||||||
|
import androidx.compose.material.icons.filled.LockOpen
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderColors
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LockableSlider(
|
||||||
|
value: Float,
|
||||||
|
onValueChange: (Float) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
|
||||||
|
steps: Int = 0,
|
||||||
|
onValueChangeFinished: (() -> Unit)? = null,
|
||||||
|
colors: SliderColors = SliderDefaults.colors(),
|
||||||
|
) {
|
||||||
|
var unlocked by remember { mutableStateOf(false) }
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { unlocked = !unlocked },
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
if (unlocked) Icons.Default.LockOpen else Icons.Default.Lock,
|
||||||
|
contentDescription = if (unlocked) "잠금 해제됨 — 다시 탭하여 잠금" else "잠김 — 탭하여 해제 후 조정",
|
||||||
|
tint = if (unlocked) Color(0xFF4CAF50) else Color.Gray.copy(alpha = 0.5f),
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Slider(
|
||||||
|
value = value,
|
||||||
|
onValueChange = if (unlocked) onValueChange else { _ -> },
|
||||||
|
onValueChangeFinished = onValueChangeFinished,
|
||||||
|
valueRange = valueRange,
|
||||||
|
steps = steps,
|
||||||
|
colors = colors,
|
||||||
|
enabled = unlocked,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-17
@@ -55,6 +55,7 @@ import com.example.medilightv2android.models.UrgencyLevel
|
|||||||
import com.example.medilightv2android.services.ScanLogStore
|
import com.example.medilightv2android.services.ScanLogStore
|
||||||
import com.example.medilightv2android.ui.components.BladdyRiveView
|
import com.example.medilightv2android.ui.components.BladdyRiveView
|
||||||
import com.example.medilightv2android.ui.components.DonutChartView
|
import com.example.medilightv2android.ui.components.DonutChartView
|
||||||
|
import com.example.medilightv2android.ui.components.LockableSlider
|
||||||
import com.example.medilightv2android.ui.components.ScanLogDialog
|
import com.example.medilightv2android.ui.components.ScanLogDialog
|
||||||
import com.example.medilightv2android.R
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
@@ -1651,7 +1652,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text("${appState.piezoSettings.maxVolume} mL", fontSize = labelSize, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("${appState.piezoSettings.maxVolume} mL", fontSize = labelSize, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = appState.piezoSettings.maxVolume.toFloat(),
|
value = appState.piezoSettings.maxVolume.toFloat(),
|
||||||
onValueChange = { appState.piezoSettings = appState.piezoSettings.copy(maxVolume = it.toInt()) },
|
onValueChange = { appState.piezoSettings = appState.piezoSettings.copy(maxVolume = it.toInt()) },
|
||||||
valueRange = 200f..800f,
|
valueRange = 200f..800f,
|
||||||
@@ -1696,7 +1697,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text("Level ${appState.bladderLevel.catheterizationThreshold}", fontSize = labelSize, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("Level ${appState.bladderLevel.catheterizationThreshold}", fontSize = labelSize, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = appState.bladderLevel.catheterizationThreshold.toFloat(),
|
value = appState.bladderLevel.catheterizationThreshold.toFloat(),
|
||||||
onValueChange = { appState.updateCatheterizationThreshold(it.toInt()) },
|
onValueChange = { appState.updateCatheterizationThreshold(it.toInt()) },
|
||||||
valueRange = 1f..10f,
|
valueRange = 1f..10f,
|
||||||
@@ -1743,7 +1744,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = valueClamped.toFloat(),
|
value = valueClamped.toFloat(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
val snapped = ((it * 2).roundToInt() / 2.0).coerceIn(0.5, 10.0)
|
val snapped = ((it * 2).roundToInt() / 2.0).coerceIn(0.5, 10.0)
|
||||||
@@ -1779,7 +1780,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!otsuEnabled) {
|
if (!otsuEnabled) {
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = currentThreshold,
|
value = currentThreshold,
|
||||||
onValueChange = { currentThreshold = it; GreenZoneConstants.lowEchoAmp = it },
|
onValueChange = { currentThreshold = it; GreenZoneConstants.lowEchoAmp = it },
|
||||||
valueRange = 800f..2000f,
|
valueRange = 800f..2000f,
|
||||||
@@ -1888,7 +1889,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Text("${postMaxVal.toInt()} / 100", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("${postMaxVal.toInt()} / 100", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = postMaxVal,
|
value = postMaxVal,
|
||||||
onValueChange = { postMaxVal = it; GreenZoneConstants.postMaxIdx = it.toInt() },
|
onValueChange = { postMaxVal = it; GreenZoneConstants.postMaxIdx = it.toInt() },
|
||||||
valueRange = 50f..95f,
|
valueRange = 50f..95f,
|
||||||
@@ -1907,7 +1908,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text("|pitch|≤${pitchThrVal.toInt()}°→Lying", fontSize = 11.sp, color = MlSecondaryText)
|
Text("|pitch|≤${pitchThrVal.toInt()}°→Lying", fontSize = 11.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = pitchThrVal,
|
value = pitchThrVal,
|
||||||
onValueChange = { pitchThrVal = it; GreenZoneConstants.pitchLyingThreshold = it
|
onValueChange = { pitchThrVal = it; GreenZoneConstants.pitchLyingThreshold = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context) },
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context) },
|
||||||
@@ -1956,7 +1957,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text("이상 lying 후 일어나면 알람", fontSize = 10.sp, color = MlSecondaryText)
|
Text("이상 lying 후 일어나면 알람", fontSize = 10.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = lyingDurVal,
|
value = lyingDurVal,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
lyingDurVal = it
|
lyingDurVal = it
|
||||||
@@ -1981,7 +1982,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text("자세 갱신 주기", fontSize = 10.sp, color = MlSecondaryText)
|
Text("자세 갱신 주기", fontSize = 10.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = pollIntervalVal,
|
value = pollIntervalVal,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
pollIntervalVal = it
|
pollIntervalVal = it
|
||||||
@@ -2004,7 +2005,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("${walkPitchVal.toInt()}°", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("${walkPitchVal.toInt()}°", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkPitchVal, onValueChange = {
|
LockableSlider(value = walkPitchVal, onValueChange = {
|
||||||
walkPitchVal = it; GreenZoneConstants.walkingMinPitchDeg = it
|
walkPitchVal = it; GreenZoneConstants.walkingMinPitchDeg = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 20f..70f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 20f..70f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2015,7 +2016,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f".format(walkStartWmean), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f".format(walkStartWmean), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkStartWmean, onValueChange = {
|
LockableSlider(value = walkStartWmean, onValueChange = {
|
||||||
walkStartWmean = it; GreenZoneConstants.walkingStartWmean = it
|
walkStartWmean = it; GreenZoneConstants.walkingStartWmean = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 1f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 1f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2026,7 +2027,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f".format(walkStartWstd), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f".format(walkStartWstd), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkStartWstd, onValueChange = {
|
LockableSlider(value = walkStartWstd, onValueChange = {
|
||||||
walkStartWstd = it; GreenZoneConstants.walkingStartWstd = it
|
walkStartWstd = it; GreenZoneConstants.walkingStartWstd = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2037,7 +2038,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f".format(walkKeepWmean), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f".format(walkKeepWmean), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkKeepWmean, onValueChange = {
|
LockableSlider(value = walkKeepWmean, onValueChange = {
|
||||||
walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it
|
walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2049,7 +2050,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f초".format(walkStartHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f초".format(walkStartHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkStartHold, onValueChange = {
|
LockableSlider(value = walkStartHold, onValueChange = {
|
||||||
walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong()
|
walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong()
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2060,7 +2061,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f초".format(walkKeepHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f초".format(walkKeepHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkKeepHold, onValueChange = {
|
LockableSlider(value = walkKeepHold, onValueChange = {
|
||||||
walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong()
|
walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong()
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2071,7 +2072,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.1f초".format(walkWindow), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.1f초".format(walkWindow), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = walkWindow, onValueChange = {
|
LockableSlider(value = walkWindow, onValueChange = {
|
||||||
walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong()
|
walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong()
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2087,7 +2088,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.2fg".format(accMagMin), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.2fg".format(accMagMin), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = accMagMin, onValueChange = {
|
LockableSlider(value = accMagMin, onValueChange = {
|
||||||
accMagMin = it; GreenZoneConstants.postureAccMagMin = it
|
accMagMin = it; GreenZoneConstants.postureAccMagMin = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
@@ -2098,7 +2099,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("%.2fg".format(accMagMax), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("%.2fg".format(accMagMax), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(value = accMagMax, onValueChange = {
|
LockableSlider(value = accMagMax, onValueChange = {
|
||||||
accMagMax = it; GreenZoneConstants.postureAccMagMax = it
|
accMagMax = it; GreenZoneConstants.postureAccMagMax = it
|
||||||
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
|
||||||
}, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
}, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
|
||||||
|
|||||||
+15
-9
@@ -121,16 +121,15 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
var sweepLastDetected by remember { mutableIntStateOf(0) }
|
var sweepLastDetected by remember { mutableIntStateOf(0) }
|
||||||
var sweepEverSawAll by remember { mutableStateOf(false) }
|
var sweepEverSawAll by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 화면 진입 시 전체 상태 리셋. clinical alignment session (placementFromMonitoring=false +
|
// 화면 진입 시 전체 상태 리셋. V2 (4-stage Method D) 를 default 로 사용 — 모든 진입점에서.
|
||||||
// 활성 ALIGNMENT 세션) 이면 V2, 그 외 (PiezoMonitoring 에서 진입 / 위저드 진입 / 잔여 세션) 는 항상 V1.
|
// (이전: clinical alignment 만 V2, 그 외 V1 → 사용자 경험 분산. 통일.)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val sess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
|
val sess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
|
||||||
val isClinicalAlign = !appState.placementFromMonitoring &&
|
val isClinicalAlign = !appState.placementFromMonitoring &&
|
||||||
sess != null &&
|
sess != null &&
|
||||||
sess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT
|
sess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT
|
||||||
com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo =
|
com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo =
|
||||||
if (isClinicalAlign) com.example.medilightv2android.managers.AlignmentAlgo.V2
|
com.example.medilightv2android.managers.AlignmentAlgo.V2
|
||||||
else com.example.medilightv2android.managers.AlignmentAlgo.V1
|
|
||||||
v2Aligner.reset()
|
v2Aligner.reset()
|
||||||
// Clinical alignment session 진입 시 — 자동으로 scan 시작 (Step 0 baseline 캡쳐).
|
// Clinical alignment session 진입 시 — 자동으로 scan 시작 (Step 0 baseline 캡쳐).
|
||||||
// waitingForStart 는 true 유지 — 사용자는 여전히 Start 눌러 알고리즘 시작 결정.
|
// waitingForStart 는 true 유지 — 사용자는 여전히 Start 눌러 알고리즘 시작 결정.
|
||||||
@@ -887,18 +886,25 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
placementScore = 5
|
placementScore = 5
|
||||||
} else {
|
} else {
|
||||||
directionHint = st.msg
|
directionHint = st.msg
|
||||||
|
// STOP 액션은 두 경우 — CH3_STABILIZE 의 "위치 유지" (still in progress) vs
|
||||||
|
// LR_BALANCE 의 "정렬 완료" (truly done). 후자만 finalStop 으로 GREEN lock.
|
||||||
|
val finalStop = st.action == com.example.medilightv2android.managers.AlignAction.STOP &&
|
||||||
|
v2Aligner.phase == com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.LR_BALANCE
|
||||||
directionIcon = when (st.action) {
|
directionIcon = when (st.action) {
|
||||||
com.example.medilightv2android.managers.AlignAction.MOVE_UP -> "arrow.up"
|
com.example.medilightv2android.managers.AlignAction.MOVE_UP -> "arrow.up"
|
||||||
com.example.medilightv2android.managers.AlignAction.MOVE_DOWN -> "arrow.down"
|
com.example.medilightv2android.managers.AlignAction.MOVE_DOWN -> "arrow.down"
|
||||||
com.example.medilightv2android.managers.AlignAction.MOVE_LEFT -> "arrow.left"
|
com.example.medilightv2android.managers.AlignAction.MOVE_LEFT -> "arrow.left"
|
||||||
com.example.medilightv2android.managers.AlignAction.MOVE_RIGHT -> "arrow.right"
|
com.example.medilightv2android.managers.AlignAction.MOVE_RIGHT -> "arrow.right"
|
||||||
com.example.medilightv2android.managers.AlignAction.PROBE_LR -> "arrow.left"
|
com.example.medilightv2android.managers.AlignAction.PROBE_LR -> "" // ↔ — 한쪽 화살표 안 어울림. msg 의 ↔ 만 표시
|
||||||
com.example.medilightv2android.managers.AlignAction.STOP -> "checkmark.circle"
|
com.example.medilightv2android.managers.AlignAction.STOP -> if (finalStop) "checkmark.circle" else ""
|
||||||
null -> ""
|
null -> ""
|
||||||
}
|
}
|
||||||
placementScore = if (st.action ==
|
placementScore = when {
|
||||||
com.example.medilightv2android.managers.AlignAction.STOP) 100 else 40
|
finalStop -> 100
|
||||||
if (st.action == com.example.medilightv2android.managers.AlignAction.STOP) {
|
st.action == com.example.medilightv2android.managers.AlignAction.STOP -> 60 // CH3_STABILIZE 유지 중
|
||||||
|
else -> 40
|
||||||
|
}
|
||||||
|
if (finalStop) {
|
||||||
if (!isLocked) greenLockedAtMs = System.currentTimeMillis()
|
if (!isLocked) greenLockedAtMs = System.currentTimeMillis()
|
||||||
isLocked = true
|
isLocked = true
|
||||||
placementPhase = PlacementPhase.GREEN
|
placementPhase = PlacementPhase.GREEN
|
||||||
|
|||||||
+3
-2
@@ -20,6 +20,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.services.ReminderService
|
import com.example.medilightv2android.services.ReminderService
|
||||||
|
import com.example.medilightv2android.ui.components.LockableSlider
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -137,7 +138,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
Text("시작", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
Text("시작", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
||||||
Text("${startHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("${startHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = startHour.toFloat(),
|
value = startHour.toFloat(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
startHour = it.toInt()
|
startHour = it.toInt()
|
||||||
@@ -159,7 +160,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
Text("종료", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
Text("종료", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
||||||
Text("${endHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text("${endHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Slider(
|
LockableSlider(
|
||||||
value = endHour.toFloat(),
|
value = endHour.toFloat(),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
endHour = it.toInt()
|
endHour = it.toInt()
|
||||||
|
|||||||
Reference in New Issue
Block a user