From 2ae5eecb2016c2c2cd432c0fdd5e7eaf5b9b9224 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 4 Jun 2026 16:15:39 +0900 Subject: [PATCH] =?UTF-8?q?Setup:=20Auto=20Scan=20Interval=EC=9D=84=20?= =?UTF-8?q?=EB=B3=84=EB=8F=84=20=ED=99=94=EB=A9=B4(AUTO=5FSCAN=5FSETUP)?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사내 피드백: PiezoPersonalization 한 화면에 카드 3개를 모은 게 아니라 MaxCapacity 다음 단계로 Auto Scan Interval 화면을 독립적으로 띄우길 원함. 흐름 변경: before: DeviceScan → PiezoPersonalization (MaxCapacity + Catheter + AutoScan) → Placement after: DeviceScan → PiezoPersonalization (MaxCapacity + Catheter) → AutoScanSetup → Placement 변경: - PiezoPersonalizationView 원복: 2카드 / verticalScroll 제거 / spacing 64dp 중앙 정렬 - AutoScanIntervalSetupView 신규 — Bladdy hero + 큰 카드 + Continue 버튼 - AppScreen.AUTO_SCAN_SETUP enum 추가 - personalizationComplete() → AUTO_SCAN_SETUP으로 이동 - autoScanSetupComplete() 신설 — PlacementGuide로 진행, piezoSettings persist - backFromPlacementGuide() — setup 흐름이면 AUTO_SCAN_SETUP으로 돌아감 - MainActivity 라우팅 + Back handler 추가 (AUTO_SCAN_SETUP → PiezoPersonalization) PiezoMonitoring의 in-monitoring settings 슬라이더는 그대로 유지 — 측정 중에도 같은 PiezoSettings.autoScanIntervalMs 값을 조정 가능. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../example/medilightv2android/AppState.kt | 12 +- .../medilightv2android/MainActivity.kt | 3 + .../AutoScanIntervalSetupView.kt | 175 ++++++++++++++++++ .../PiezoPersonalizationView.kt | 60 +----- 4 files changed, 190 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/com/example/medilightv2android/ui/views/personalization/AutoScanIntervalSetupView.kt diff --git a/app/src/main/java/com/example/medilightv2android/AppState.kt b/app/src/main/java/com/example/medilightv2android/AppState.kt index 26822e6..762cfcc 100644 --- a/app/src/main/java/com/example/medilightv2android/AppState.kt +++ b/app/src/main/java/com/example/medilightv2android/AppState.kt @@ -12,7 +12,8 @@ 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, + NIRS_CALIBRATION, PIEZO_PERSONALIZATION, AUTO_SCAN_SETUP, // ★ MaxCapacity 다음 단계 — Auto Scan Interval 별도 화면 + PLACEMENT_GUIDE, PIN_SETUP, PIN_ENTRY, VIVAMYO_MONITORING, PIEZO_MONITORING, MEASUREMENT_HISTORY, VOIDING_DIARY, URINE_CAMERA, REMINDER_SETTINGS, CLINICAL_HOME, // ★ Dev 모드 한정 — 임상 R&D 측정 세션 화면 (입력 폼) @@ -135,6 +136,13 @@ class AppState(private val context: Context) { fun personalizationComplete() { storage.piezoSettings = piezoSettings placementFromMonitoring = false + // MaxCapacity 화면 다음에 AutoScan Interval 설정 화면을 거쳐 Placement로 진행 + currentScreen = AppScreen.AUTO_SCAN_SETUP + } + + /** Auto Scan Interval 설정 화면 → Placement Guide */ + fun autoScanSetupComplete() { + storage.piezoSettings = piezoSettings currentScreen = AppScreen.PLACEMENT_GUIDE } @@ -161,7 +169,7 @@ class AppState(private val context: Context) { fun backFromPlacementGuide() { currentScreen = if (placementFromMonitoring) AppScreen.PIEZO_MONITORING - else AppScreen.PIEZO_PERSONALIZATION + else AppScreen.AUTO_SCAN_SETUP // setup 흐름: Placement ← AutoScan ← MaxCapacity placementFromMonitoring = false } diff --git a/app/src/main/java/com/example/medilightv2android/MainActivity.kt b/app/src/main/java/com/example/medilightv2android/MainActivity.kt index b2a88a4..8440001 100644 --- a/app/src/main/java/com/example/medilightv2android/MainActivity.kt +++ b/app/src/main/java/com/example/medilightv2android/MainActivity.kt @@ -32,6 +32,7 @@ import com.example.medilightv2android.ui.views.monitoring.VivamyoMonitoringView import com.example.medilightv2android.ui.views.monitoring.UrineCameraScreen import com.example.medilightv2android.ui.views.monitoring.VoidingDiaryView import com.example.medilightv2android.ui.views.onboarding.OnboardingView +import com.example.medilightv2android.ui.views.personalization.AutoScanIntervalSetupView import com.example.medilightv2android.ui.views.personalization.PiezoPersonalizationView import com.example.medilightv2android.ui.views.pin.PinMode import com.example.medilightv2android.ui.views.pin.PinView @@ -103,6 +104,7 @@ fun MedilightApp(modifier: Modifier = Modifier) { AppScreen.CLINICAL_HOME else AppScreen.HOME AppScreen.PIEZO_PERSONALIZATION -> appState.currentScreen = AppScreen.DEVICE_SCAN + AppScreen.AUTO_SCAN_SETUP -> appState.currentScreen = AppScreen.PIEZO_PERSONALIZATION // 임상 모드 측정 중이면 endMeasurement → ClinicalHome 복귀 AppScreen.PLACEMENT_GUIDE -> if (com.example.medilightv2android.services.ClinicalSessionStore.currentSession != null) { @@ -152,6 +154,7 @@ fun MedilightApp(modifier: Modifier = Modifier) { AppScreen.DEVICE_SCAN -> DeviceScanView(appState) AppScreen.NIRS_CALIBRATION -> CalibrationView(appState) AppScreen.PIEZO_PERSONALIZATION -> PiezoPersonalizationView(appState) + AppScreen.AUTO_SCAN_SETUP -> AutoScanIntervalSetupView(appState) AppScreen.PLACEMENT_GUIDE -> PlacementGuideView(appState) AppScreen.PIN_SETUP -> PinView(appState, PinMode.SETUP, context) AppScreen.PIN_ENTRY -> PinView(appState, PinMode.ENTER, context) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/personalization/AutoScanIntervalSetupView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/personalization/AutoScanIntervalSetupView.kt new file mode 100644 index 0000000..cdbd134 --- /dev/null +++ b/app/src/main/java/com/example/medilightv2android/ui/views/personalization/AutoScanIntervalSetupView.kt @@ -0,0 +1,175 @@ +package com.example.medilightv2android.ui.views.personalization + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min +import androidx.compose.ui.unit.sp +import com.example.medilightv2android.AppScreen +import com.example.medilightv2android.AppState +import com.example.medilightv2android.ui.components.BladdyIdleRiveView +import com.example.medilightv2android.ui.theme.* +import kotlin.math.roundToInt + +/** + * Auto Scan Interval 설정 화면. + * + * 흐름: PiezoPersonalization(MaxCapacity + CatheterThreshold) + * → 본 화면 (Auto Scan Interval) + * → PlacementGuide → PiezoMonitoring + * + * 600~5000ms 범위, 200ms 단위 snap. PiezoMonitoring의 in-monitoring settings에서 + * 같은 슬라이더로 측정 중에도 조정 가능. + */ +@Composable +fun AutoScanIntervalSetupView(appState: AppState) { + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + // Header + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = { appState.currentScreen = AppScreen.PIEZO_PERSONALIZATION }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary) + } + } + + // Bladdy hero — Personalization과 동일 톤 + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + contentAlignment = Alignment.Center + ) { + val bladdySize = min(maxWidth, 220.dp) + BladdyIdleRiveView(size = bladdySize) + } + + // 카드 영역 — 중앙 정렬 + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + AutoScanIntervalCard(appState) + } + + // Continue button + Button( + onClick = { appState.autoScanSetupComplete() }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + .padding(bottom = 32.dp) + .height(56.dp), + shape = RoundedCornerShape(16.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), + contentPadding = PaddingValues() + ) { + Box( + modifier = Modifier.fillMaxSize().background( + Brush.horizontalGradient(listOf(MlTeal, MlPrimary)), + RoundedCornerShape(16.dp) + ), + contentAlignment = Alignment.Center + ) { + Text("Continue", fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = Color.White) + } + } + } +} + +@Composable +private fun AutoScanIntervalCard(appState: AppState) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MlPrimary.copy(alpha = 0.06f)) + ) { + Column( + modifier = Modifier.padding(horizontal = 20.dp, vertical = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Auto Scan Interval", fontSize = 34.sp, fontWeight = FontWeight.ExtraBold) + + Spacer(modifier = Modifier.height(8.dp)) + Text("Auto scan 사이클 간격 — 짧을수록 빠른 갱신, 길수록 안정", + fontSize = 13.sp, color = MlSecondaryText) + + Spacer(modifier = Modifier.height(20.dp)) + + Row(verticalAlignment = Alignment.Bottom) { + Text("${appState.piezoSettings.autoScanIntervalMs}", fontSize = 64.sp, + fontWeight = FontWeight.ExtraBold, color = MlPrimary) + Spacer(modifier = Modifier.width(8.dp)) + Text("ms", fontSize = 24.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText, + modifier = Modifier.padding(bottom = 12.dp)) + } + + Spacer(modifier = Modifier.height(12.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) { + StepperButton(icon = Icons.Default.Remove) { + val next = (appState.piezoSettings.autoScanIntervalMs - 200).coerceIn(600, 5000) + appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next) + } + StepperButton(icon = Icons.Default.Add) { + val next = (appState.piezoSettings.autoScanIntervalMs + 200).coerceIn(600, 5000) + appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Slider( + value = appState.piezoSettings.autoScanIntervalMs.toFloat(), + onValueChange = { + val snapped = ((it / 200f).roundToInt() * 200).coerceIn(600, 5000) + appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = snapped) + }, + valueRange = 600f..5000f, + steps = 21, + modifier = Modifier.padding(horizontal = 24.dp), + colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary) + ) + } + } +} + +@Composable +private fun StepperButton(icon: androidx.compose.ui.graphics.vector.ImageVector, onClick: () -> Unit) { + Box( + modifier = Modifier + .size(56.dp) + .clip(CircleShape) + .background(MlPrimary) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center + ) { + Icon(icon, null, tint = Color.White, modifier = Modifier.size(28.dp)) + } +} diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/personalization/PiezoPersonalizationView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/personalization/PiezoPersonalizationView.kt index 287ddff..7093690 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/personalization/PiezoPersonalizationView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/personalization/PiezoPersonalizationView.kt @@ -6,8 +6,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.border import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -68,21 +66,17 @@ fun PiezoPersonalizationView(appState: AppState) { BladdyIdleRiveView(size = bladdySize) } - // 카드 영역 — 3카드 스크롤 가능 + // 카드 영역 — 수직 중앙 정렬 Column( modifier = Modifier .weight(1f) .fillMaxWidth() - .verticalScroll(rememberScrollState()) .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), + verticalArrangement = Arrangement.spacedBy(64.dp, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(8.dp)) MaxVolumeCard(appState) CatheterThresholdCard(appState) - AutoScanIntervalCard(appState) - Spacer(modifier = Modifier.height(8.dp)) } // Continue button @@ -219,56 +213,6 @@ private fun CatheterThresholdCard(appState: AppState) { } } -@Composable -private fun AutoScanIntervalCard(appState: AppState) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(20.dp), - colors = CardDefaults.cardColors(containerColor = MlPrimary.copy(alpha = 0.04f)) - ) { - Column( - modifier = Modifier.padding(horizontal = 20.dp, vertical = 18.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Auto Scan Interval", fontSize = 28.sp, fontWeight = FontWeight.ExtraBold) - Spacer(modifier = Modifier.height(8.dp)) - Text("Auto scan 사이클 간격 (ms). 짧을수록 빠른 갱신, 길수록 안정.", - fontSize = 12.sp, color = MlSecondaryText) - Spacer(modifier = Modifier.height(12.dp)) - Row(verticalAlignment = Alignment.Bottom) { - Text("${appState.piezoSettings.autoScanIntervalMs}", fontSize = 48.sp, - fontWeight = FontWeight.ExtraBold, color = MlPrimary) - Spacer(modifier = Modifier.width(6.dp)) - Text("ms", fontSize = 20.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText, - modifier = Modifier.padding(bottom = 8.dp)) - } - Spacer(modifier = Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) { - StepperButton(icon = Icons.Default.Remove) { - val next = (appState.piezoSettings.autoScanIntervalMs - 200).coerceIn(600, 5000) - appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next) - } - StepperButton(icon = Icons.Default.Add) { - val next = (appState.piezoSettings.autoScanIntervalMs + 200).coerceIn(600, 5000) - appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = next) - } - } - Spacer(modifier = Modifier.height(8.dp)) - Slider( - value = appState.piezoSettings.autoScanIntervalMs.toFloat(), - onValueChange = { - val snapped = ((it / 200f).roundToInt() * 200).coerceIn(600, 5000) - appState.piezoSettings = appState.piezoSettings.copy(autoScanIntervalMs = snapped) - }, - valueRange = 600f..5000f, - steps = 21, - modifier = Modifier.padding(horizontal = 24.dp), - colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary) - ) - } - } -} - @Composable private fun StepperButton(icon: androidx.compose.ui.graphics.vector.ImageVector, onClick: () -> Unit) { Box(