Setup에 Auto Scan Interval 카드 추가 + BLE 끊김 banner 제거 (silent reconnect)
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) <noreply@anthropic.com>
This commit is contained in:
+5
-21
@@ -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) {
|
||||
|
||||
+2
-20
@@ -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)) }
|
||||
|
||||
+58
-2
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user