From 9b090a4c8ea21d788f541620b71667eeb1d2a895 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 4 Jun 2026 14:50:23 +0900 Subject: [PATCH] =?UTF-8?q?Setup=EC=97=90=20Auto=20Scan=20Interval=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EC=B6=94=EA=B0=80=20+=20BLE=20=EB=81=8A?= =?UTF-8?q?=EA=B9=80=20banner=20=EC=A0=9C=EA=B1=B0=20(silent=20reconnect)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PiezoPersonalizationView: - AutoScanIntervalCard 신규 — 600~5000ms 슬라이더 + ±200ms 스테퍼 기존 MaxVolume / CatheterThreshold 카드와 동일한 톤 - 3카드가 화면을 넘칠 수 있어 verticalScroll로 전환, spacing 64→24dp PiezoMonitoringView / VivamyoMonitoringView: - 사내 테스트 피드백: BLE 끊김 banner가 자주 튀어 거슬림. BleManager 측 scheduleAutoReconnect()가 이미 백그라운드로 재연결을 시도하므로 UI 알림은 불필요. - onUnexpectedDisconnect/onReconnectionFailed/onConnectionStateChanged 콜백에서 banner state 토글 제거. 측정 일시정지 및 batteryPolling 재시작은 그대로 유지. - 미사용 state (showDisconnectionBanner, reconnectionState) + 관련 import 정리. BleDisconnectionBanner.kt 파일 자체는 후속 사용 가능성 위해 남김. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../views/monitoring/PiezoMonitoringView.kt | 26 ++------ .../views/monitoring/VivamyoMonitoringView.kt | 22 +------ .../PiezoPersonalizationView.kt | 60 ++++++++++++++++++- 3 files changed, 65 insertions(+), 43 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 290125a..a397d23 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 @@ -52,8 +52,6 @@ import com.example.medilightv2android.models.BladderLevel import com.example.medilightv2android.models.UrgencyLevel import com.example.medilightv2android.services.ScanLogStore import com.example.medilightv2android.ui.components.BladdyRiveView -import com.example.medilightv2android.ui.components.BleDisconnectionBanner -import com.example.medilightv2android.ui.components.BleReconnectionState import com.example.medilightv2android.ui.components.DonutChartView import com.example.medilightv2android.ui.components.ScanLogDialog import com.example.medilightv2android.R @@ -75,8 +73,6 @@ fun PiezoMonitoringView(appState: AppState) { var showVoiceInput by remember { mutableStateOf(false) } var showUrineCamera by remember { mutableStateOf(false) } var showAddCatheterDialog by remember { mutableStateOf(false) } - var showDisconnectionBanner by remember { mutableStateOf(false) } - var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) } var isAutoMeasuring by remember { mutableStateOf(false) } var showDevPanels by remember { mutableStateOf(false) } // Charles: Scan Log dialog + Bladdy 3-tap dev toggle @@ -560,9 +556,9 @@ fun PiezoMonitoringView(appState: AppState) { // Setup disconnect handling — auto reconnect integrated LaunchedEffect(Unit) { + // BLE 끊김 시 banner/alert 띄우지 않고 BleManager 측 자동 재연결만 동작. + // 측정만 일시정지 — 연결 복구되면 onConnectionStateChanged에서 자동 후처리. bleManager.onUnexpectedDisconnect = { - reconnectionState = BleReconnectionState.RECONNECTING - showDisconnectionBanner = true if (isMeasuring) { isMeasuring = false measureRetryCount = 0 @@ -570,12 +566,10 @@ fun PiezoMonitoringView(appState: AppState) { if (isAutoMeasuring) stopAutoMeasure() } bleManager.onReconnectionFailed = { - reconnectionState = BleReconnectionState.FAILED + // 실패해도 UI 알림 없음 — BleManager가 다음 주기에 또 재시도하거나 idle. } bleManager.onConnectionStateChanged = { connected -> - if (connected && showDisconnectionBanner) { - showDisconnectionBanner = false - reconnectionState = BleReconnectionState.DISCONNECTED + if (connected) { bleManager.startBatteryPolling() } } @@ -941,17 +935,7 @@ fun PiezoMonitoringView(appState: AppState) { } } - if (showDisconnectionBanner) { - BleDisconnectionBanner( - state = reconnectionState, - onReconnect = { - reconnectionState = BleReconnectionState.RECONNECTING - bleManager.cancelAutoReconnect() - bleManager.reconnectToLastDevice(bleManager.lastConnectedAddress) - } - ) - Spacer(modifier = Modifier.height(8.dp)) - } + // BLE 끊김 banner는 사내 테스트 피드백으로 제거됨 — 백그라운드 자동 재연결만 유지. val scrollState = rememberScrollState() LaunchedEffect(showSettings) { diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt index 220d153..349d91b 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt @@ -34,8 +34,6 @@ import com.example.medilightv2android.managers.NirsManager import com.example.medilightv2android.models.NirsMcjPacket import com.example.medilightv2android.models.NirsOxyResult import com.example.medilightv2android.ui.components.BladdyIdleRiveView -import com.example.medilightv2android.ui.components.BleDisconnectionBanner -import com.example.medilightv2android.ui.components.BleReconnectionState import com.example.medilightv2android.ui.theme.* import kotlin.random.Random @@ -52,8 +50,6 @@ fun VivamyoMonitoringView(appState: AppState) { var selectedTab by remember { mutableIntStateOf(0) } var showClearConfirm by remember { mutableStateOf(false) } var isToggling by remember { mutableStateOf(false) } - var showDisconnectionBanner by remember { mutableStateOf(false) } - var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) } val batteryLvl by bleManager.batteryLevel val isConnected by bleManager.isConnected @@ -184,11 +180,9 @@ fun VivamyoMonitoringView(appState: AppState) { handler.postDelayed({ isToggling = false }, 1000) } + // BLE 끊김 banner는 사내 테스트 피드백으로 제거됨 — BleManager 측 자동 재연결만 동작. LaunchedEffect(Unit) { - bleManager.onUnexpectedDisconnect = { - reconnectionState = BleReconnectionState.DISCONNECTED - showDisconnectionBanner = true - } + bleManager.onUnexpectedDisconnect = { /* no-op: silent reconnect */ } } DisposableEffect(Unit) { onDispose { @@ -259,18 +253,6 @@ fun VivamyoMonitoringView(appState: AppState) { Box(modifier = Modifier.size(10.dp).clip(CircleShape).background(if (isDemoMode || isConnected) Color.Green else Color.Red)) } - if (showDisconnectionBanner) { - BleDisconnectionBanner( - state = reconnectionState, - onReconnect = { - reconnectionState = BleReconnectionState.RECONNECTING - bleManager.reconnectToLastDevice( - com.example.medilightv2android.services.UserStorage.getInstance(context).lastDeviceAddress - ) - } - ) - } - // Tab picker TabRow(selectedTabIndex = selectedTab, modifier = Modifier.padding(horizontal = hPad)) { Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }) { Text("SpO2", modifier = Modifier.padding(if (isCompact) 8.dp else 12.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 7093690..287ddff 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,6 +6,8 @@ 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 @@ -66,17 +68,21 @@ fun PiezoPersonalizationView(appState: AppState) { BladdyIdleRiveView(size = bladdySize) } - // 카드 영역 — 수직 중앙 정렬 + // 카드 영역 — 3카드 스크롤 가능 Column( modifier = Modifier .weight(1f) .fillMaxWidth() + .verticalScroll(rememberScrollState()) .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(64.dp, Alignment.CenterVertically), + verticalArrangement = Arrangement.spacedBy(24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { + Spacer(modifier = Modifier.height(8.dp)) MaxVolumeCard(appState) CatheterThresholdCard(appState) + AutoScanIntervalCard(appState) + Spacer(modifier = Modifier.height(8.dp)) } // Continue button @@ -213,6 +219,56 @@ 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(