Files
VesiscanClinicalAndroid/app/src/main/java/com/example/medilightv2android/AppState.kt
T
dw.jang 82ba3bbcf6 UI 묶음 E: Auto Scan Interval 통합 + 0.5단위/sec·min 토글 우측 (회의 #8 #9 #10)
회의 #8 페이지 통합
  - 별도 화면(AUTO_SCAN_SETUP) 폐기 — Personalization 한 화면에 3카드:
    1) Max Capacity, 2) Catheter Threshold, 3) Auto Scan Interval
  - 캐릭터 작게 (220→120dp) + 카드 영역 verticalScroll
  - 'Scan Interval' → 'Auto Scan Interval' 이름 복귀
  - 한글 설명 제거
  - 흐름: PiezoPersonalization → PlacementGuide (AUTO_SCAN_SETUP 단계 없음)

회의 #9 새 형식 — 모든 위치에서 통일
  - 범위 0.5 ~ 10 (sec/min 모드 동일)
  - 0.5 단위 (이전 200ms/30s 단위)
  - sec/min 토글 숫자 우측에 위치
  - SEC 모드: ms = value × 1000 (예: 1.5sec = 1500ms)
  - MIN 모드: ms = value × 60000 (예: 1.5min = 90000ms)
  - 모드 전환 시 같은 숫자 값 유지 (1.5sec ↔ 1.5min)

회의 #10 in-monitoring settings 동일 적용
  PiezoMonitoringView 설정 페이지의 Auto Scan Interval 슬라이더도 새 형식으로 갱신.
  같은 로직 (unitMode + valueClamped + sec/min 토글 + 0.5 step).

정리:
  - AppState.AUTO_SCAN_SETUP enum value 제거
  - autoScanSetupComplete() 함수 제거 (호출처 없음)
  - backFromPlacementGuide() → PIEZO_PERSONALIZATION으로 (AUTO_SCAN_SETUP 단계 폐기)
  - MainActivity 라우팅/Back handler에서 AUTO_SCAN_SETUP 분기 제거
  - AutoScanIntervalSetupView.kt 파일 삭제

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 17:09:24 +09:00

264 lines
9.2 KiB
Kotlin

package com.example.medilightv2android
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.managers.NirsManager
import com.example.medilightv2android.models.*
import com.example.medilightv2android.services.*
enum class AppScreen {
ONBOARDING, CONSENT, REGISTRATION, HOME, SENSOR_SELECT, DEVICE_SCAN,
NIRS_CALIBRATION, PIEZO_PERSONALIZATION,
PLACEMENT_GUIDE, PIN_SETUP, PIN_ENTRY,
VIVAMYO_MONITORING, PIEZO_MONITORING, MEASUREMENT_HISTORY,
VOIDING_DIARY, URINE_CAMERA, REMINDER_SETTINGS,
CLINICAL_HOME, // ★ Dev 모드 한정 — 임상 R&D 측정 세션 화면 (입력 폼)
CLINICAL_LIVE // ★ Dev 모드 한정 — 6채널 라이브 시각화 + Capture
}
class AppState(private val context: Context) {
var currentScreen by mutableStateOf(AppScreen.ONBOARDING)
var user by mutableStateOf(User())
var sensorMode by mutableStateOf(SensorMode.PIEZO)
var bladderLevel by mutableStateOf(BladderLevel())
var isDeviceConnected by mutableStateOf(false)
var isDemoMode by mutableStateOf(false)
var isDevMode by mutableStateOf(false)
var piezoSettings by mutableStateOf(PiezoSettings())
var catheterCount = mutableIntStateOf(0)
// Placement 진입 경로 분기용 — Monitoring에서 재진입 시 true, Personalization에서 진입 시 false
var placementFromMonitoring by mutableStateOf(false)
private val storage = UserStorage.getInstance(context)
private val historyStore = MeasurementHistoryStore.getInstance(context)
private val notificationService = NotificationService.getInstance(context)
private val bleManager = BleManager.getInstance(context)
private val reminderService = ReminderService.getInstance(context)
init {
loadState()
}
fun loadState() {
user = storage.user
sensorMode = storage.sensorMode
bladderLevel = bladderLevel.copy(
currentLevel = storage.bladderLevel,
catheterizationThreshold = storage.catheterizationLevel
)
piezoSettings = storage.piezoSettings
catheterCount.intValue = storage.catheterCount
currentScreen = when {
!storage.hasCompletedOnboarding -> AppScreen.ONBOARDING
!storage.isRegistered -> AppScreen.REGISTRATION
!storage.hasPin -> AppScreen.PIN_SETUP
else -> AppScreen.PIN_ENTRY
}
}
fun completeOnboarding() {
storage.hasCompletedOnboarding = true
storage.hasConsented = true
currentScreen = AppScreen.REGISTRATION
}
fun completeConsent() {
storage.hasConsented = true
storage.consentTimestamp = System.currentTimeMillis()
currentScreen = AppScreen.REGISTRATION
}
fun completeRegistration() {
storage.user = user
storage.isRegistered = true
currentScreen = AppScreen.PIN_SETUP
}
fun goHome() {
NirsManager.instance.stopStreaming()
NirsManager.instance.reset()
// 회의 결정: 실제 BLE 연결은 HOME에서도 유지. 다음 Start 시 설정/scan skip하고
// 바로 PiezoMonitoring으로 진입할 수 있도록.
// Demo 모드는 가짜 연결이므로 isDeviceConnected reset.
if (isDemoMode) isDeviceConnected = false
isDemoMode = false
currentScreen = AppScreen.HOME
}
fun enterDemoMode() {
isDemoMode = true
isDeviceConnected = true
currentScreen = if (sensorMode == SensorMode.NIRS) {
AppScreen.VIVAMYO_MONITORING
} else {
AppScreen.PIEZO_MONITORING
}
}
fun startFromHome() {
sensorMode = SensorMode.PIEZO
storage.sensorMode = SensorMode.PIEZO
// 회의 결정: 이미 연결된 상태이면 설정/Placement skip하고 바로 도넛차트.
// 연결되지 않은 상태에서만 DeviceScan부터 일련의 과정 진행.
currentScreen = if (isDeviceConnected) AppScreen.PIEZO_MONITORING else AppScreen.DEVICE_SCAN
}
fun selectSensor(mode: SensorMode) {
sensorMode = mode
storage.sensorMode = mode
currentScreen = AppScreen.DEVICE_SCAN
}
fun backToSensorSelect() {
currentScreen = AppScreen.HOME
}
fun deviceConnected() {
isDeviceConnected = true
// 임상 모드에서 BLE 연결 시 ClinicalHome으로 복귀 (Personalization 우회)
if (com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow) {
currentScreen = AppScreen.CLINICAL_HOME
return
}
currentScreen = if (sensorMode == SensorMode.NIRS) {
AppScreen.NIRS_CALIBRATION
} else {
AppScreen.PIEZO_PERSONALIZATION
}
}
fun calibrationComplete() {
currentScreen = AppScreen.VIVAMYO_MONITORING
}
fun personalizationComplete() {
storage.piezoSettings = piezoSettings
placementFromMonitoring = false
// 회의 #8: AutoScanInterval을 Personalization 페이지에 통합 — 별도 화면 폐기.
currentScreen = AppScreen.PLACEMENT_GUIDE
}
/** Persist piezoSettings (called from in-session settings save). */
fun savePiezoSettingsNow() {
// Re-assign to trigger the mutableStateOf delegate so any composables
// observing piezoSettings recompose with the latest value, then persist
// to disk so the change survives process death.
piezoSettings = piezoSettings.copy()
storage.piezoSettings = piezoSettings
}
fun placementComplete() {
bladderLevel = BladderLevel(currentLevel = 0, catheterizationThreshold = bladderLevel.catheterizationThreshold)
storage.bladderLevel = 0
placementFromMonitoring = false
currentScreen = AppScreen.PIEZO_MONITORING
}
fun enterPlacementFromMonitoring() {
placementFromMonitoring = true
currentScreen = AppScreen.PLACEMENT_GUIDE
}
fun backFromPlacementGuide() {
// 회의 #8: AutoScan을 Personalization으로 통합했으므로 Placement Back은
// 바로 Personalization으로 (이전 AUTO_SCAN_SETUP 단계 폐기).
currentScreen = if (placementFromMonitoring) AppScreen.PIEZO_MONITORING
else AppScreen.PIEZO_PERSONALIZATION
placementFromMonitoring = false
}
fun pinSet(pin: String) {
storage.pin = pin
storage.hasPin = true
currentScreen = AppScreen.HOME
}
fun pinVerified() {
currentScreen = AppScreen.HOME
}
fun incrementLevel() {
val previousUrgency = bladderLevel.urgency
val newLevel = bladderLevel.copy()
newLevel.increment()
bladderLevel = newLevel
storage.bladderLevel = bladderLevel.currentLevel
val record = MeasurementRecord(
eventType = MeasurementEventType.MEASUREMENT,
level = bladderLevel.currentLevel,
estimatedVolume = estimatedVolume,
urgencyLevel = bladderLevel.urgency,
fillPercentage = fillPercentage
)
historyStore.addRecord(record)
val newUrgency = bladderLevel.urgency
if (newUrgency != previousUrgency) {
notificationService.sendUrgencyNotification(newUrgency, estimatedVolume)
}
}
fun resetLevel() {
val record = MeasurementRecord(
eventType = MeasurementEventType.CATHETERIZATION,
level = bladderLevel.currentLevel,
estimatedVolume = estimatedVolume,
urgencyLevel = bladderLevel.urgency,
fillPercentage = fillPercentage
)
historyStore.addRecord(record)
val newLevel = bladderLevel.copy()
newLevel.reset()
bladderLevel = newLevel
storage.bladderLevel = 0
notificationService.cancelAll()
reminderService.reschedule()
if (catheterCount.intValue > 0) {
catheterCount.intValue--
storage.catheterCount = catheterCount.intValue
}
}
fun showHistory() {
currentScreen = AppScreen.MEASUREMENT_HISTORY
}
fun backToMonitoring() {
currentScreen = if (sensorMode == SensorMode.NIRS) AppScreen.VIVAMYO_MONITORING else AppScreen.PIEZO_MONITORING
}
fun addCatheterCount(amount: Int = 1) {
catheterCount.intValue += amount
storage.catheterCount = catheterCount.intValue
}
fun updateCatheterizationThreshold(threshold: Int) {
bladderLevel = bladderLevel.copy(catheterizationThreshold = threshold)
storage.catheterizationLevel = threshold
}
fun savePiezoSettings() {
storage.piezoSettings = piezoSettings
}
val estimatedVolume: Int
get() {
if (BladderLevel.MAX_LEVEL <= 0) return 0
return (bladderLevel.currentLevel.toDouble() / BladderLevel.MAX_LEVEL * piezoSettings.maxVolume).toInt()
}
val fillPercentage: Double
get() {
if (BladderLevel.MAX_LEVEL <= 0) return 0.0
return bladderLevel.currentLevel.toDouble() / BladderLevel.MAX_LEVEL * 100.0
}
}