diff --git a/app/src/main/java/com/example/medilightv2android/AppState.kt b/app/src/main/java/com/example/medilightv2android/AppState.kt index a4f4189..8ecd0c6 100644 --- a/app/src/main/java/com/example/medilightv2android/AppState.kt +++ b/app/src/main/java/com/example/medilightv2android/AppState.kt @@ -22,6 +22,7 @@ class AppState(private val context: Context) { var sensorMode by mutableStateOf(SensorMode.PIEZO) var bladderLevel by mutableStateOf(BladderLevel()) var isDeviceConnected by mutableStateOf(false) + var isDemoMode by mutableStateOf(false) var piezoSettings by mutableStateOf(PiezoSettings()) var catheterCount = mutableIntStateOf(0) @@ -66,11 +67,22 @@ class AppState(private val context: Context) { fun goHome() { NirsManager.instance.stopStreaming() NirsManager.instance.reset() - bleManager.disconnect() + if (!isDemoMode) bleManager.disconnect() 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() { currentScreen = AppScreen.SENSOR_SELECT } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt index 663fcdd..7009d06 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt @@ -251,6 +251,16 @@ fun DeviceScanView(appState: AppState) { } } + // Demo mode button + TextButton( + onClick = { appState.enterDemoMode() }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) { + Text("Demo Mode (No Device)", fontSize = 14.sp, color = MlSecondaryText) + } + // Scan button Button( onClick = { 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 768669e..f79cb4d 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 @@ -48,6 +48,7 @@ fun PiezoMonitoringView(appState: AppState) { var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) } var isAutoMeasuring by remember { mutableStateOf(false) } + val isDemoMode = appState.isDemoMode val urgency = appState.bladderLevel.urgency val batteryLvl by bleManager.batteryLevel val isConnected by bleManager.isConnected @@ -179,13 +180,18 @@ fun PiezoMonitoringView(appState: AppState) { } // Battery - Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText) + if (isDemoMode) { + Text("DEMO", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White, + modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 6.dp, vertical = 2.dp)) + } else { + Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText) + } Spacer(modifier = Modifier.width(4.dp)) Box( modifier = Modifier .size(10.dp) .clip(CircleShape) - .background(if (isConnected) Color.Green else Color.Red) + .background(if (isDemoMode || isConnected) Color.Green else Color.Red) ) } 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 75ffb71..6dcd5df 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 @@ -31,10 +31,12 @@ import com.example.medilightv2android.AppState import com.example.medilightv2android.ble.BleManager 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 @Composable fun VivamyoMonitoringView(appState: AppState) { @@ -56,6 +58,11 @@ fun VivamyoMonitoringView(appState: AppState) { val sampleCount by nirsManager.sampleCount val isFilterEnabled by nirsManager.isFilterEnabled + val isDemoMode = appState.isDemoMode + var demoRunnable by remember { mutableStateOf(null) } + var demoBaseHbO2 by remember { mutableStateOf(12.0) } + var demoBaseHb by remember { mutableStateOf(8.0) } + var mcjRetryCount = remember { mutableIntStateOf(0) } var mcjRetryRunnable by remember { mutableStateOf(null) } @@ -108,6 +115,39 @@ fun VivamyoMonitoringView(appState: AppState) { handler.postDelayed(initialRetry, 2000) } + fun startDemoStream() { + val tick = object : Runnable { + override fun run() { + if (!nirsManager.isStreaming.value) return + demoBaseHbO2 += Random.nextDouble(-0.3, 0.3) + demoBaseHb += Random.nextDouble(-0.2, 0.2) + val hbO2 = demoBaseHbO2.coerceIn(5.0, 25.0) + val hb = demoBaseHb.coerceIn(3.0, 15.0) + demoBaseHbO2 = hbO2 + demoBaseHb = hb + val tHb = hbO2 + hb + val stO2 = if (tHb > 0) (hbO2 / tHb * 100.0) else 0.0 + + nirsManager.currentOxy.value = NirsOxyResult(hbO2, hb, tHb, stO2) + nirsManager.sampleCount.intValue++ + nirsManager.oxyHbO2History.add(hbO2) + nirsManager.oxyHbHistory.add(hb) + if (nirsManager.oxyHbO2History.size > 240) nirsManager.oxyHbO2History.removeAt(0) + if (nirsManager.oxyHbHistory.size > 240) nirsManager.oxyHbHistory.removeAt(0) + + handler.postDelayed(this, 250) + } + } + demoRunnable = tick + handler.post(tick) + } + + fun stopDemoStream() { + demoRunnable?.let { handler.removeCallbacks(it) } + demoRunnable = null + nirsManager.stopStreaming() + } + fun stopNirsStream() { // 1. Stop polling chain & clear callback first nirsManager.stopStreaming() @@ -124,11 +164,16 @@ fun VivamyoMonitoringView(appState: AppState) { if (isToggling) return isToggling = true if (isStreaming) { - stopNirsStream() + if (isDemoMode) stopDemoStream() else stopNirsStream() } else { - bleManager.stopBatteryPolling() // Pause battery polling during streaming - nirsManager.startStreaming() - startNirsStream() + if (isDemoMode) { + nirsManager.startStreaming() + startDemoStream() + } else { + bleManager.stopBatteryPolling() + nirsManager.startStreaming() + startNirsStream() + } } handler.postDelayed({ isToggling = false }, 1000) } @@ -141,7 +186,8 @@ fun VivamyoMonitoringView(appState: AppState) { } DisposableEffect(Unit) { onDispose { - if (nirsManager.isStreaming.value) stopNirsStream() + if (isDemoMode) { stopDemoStream() } + else if (nirsManager.isStreaming.value) stopNirsStream() else { cancelMcjRetry(); bleManager.onNirsMcjReceived = null } } } @@ -196,9 +242,14 @@ fun VivamyoMonitoringView(appState: AppState) { Text("NIRS Monitoring", fontSize = 13.sp, color = MlSecondaryText) } Spacer(modifier = Modifier.weight(1f)) - Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText) + if (isDemoMode) { + Text("DEMO", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White, + modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 6.dp, vertical = 2.dp)) + } else { + Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText) + } Spacer(modifier = Modifier.width(8.dp)) - Box(modifier = Modifier.size(10.dp).clip(CircleShape).background(if (isConnected) Color.Green else Color.Red)) + Box(modifier = Modifier.size(10.dp).clip(CircleShape).background(if (isDemoMode || isConnected) Color.Green else Color.Red)) } if (showDisconnectionBanner) {