feat: responsive layout for foldable phones (compact < 400dp)
Reduce padding, icon sizes, and chart dimensions on narrow screens. Hide title text and battery % in compact mode to prevent top bar overflow. Scale donut chart 240→180dp and SpO2 text 56→40sp. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-27
@@ -20,6 +20,7 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -48,6 +49,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) }
|
var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) }
|
||||||
var isAutoMeasuring by remember { mutableStateOf(false) }
|
var isAutoMeasuring by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||||
|
val isCompact = screenWidth < 400
|
||||||
|
|
||||||
val isDemoMode = appState.isDemoMode
|
val isDemoMode = appState.isDemoMode
|
||||||
val urgency = appState.bladderLevel.urgency
|
val urgency = appState.bladderLevel.urgency
|
||||||
val batteryLvl by bleManager.batteryLevel
|
val batteryLvl by bleManager.batteryLevel
|
||||||
@@ -131,30 +135,31 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
.background(urgency.backgroundColor)
|
.background(urgency.backgroundColor)
|
||||||
) {
|
) {
|
||||||
// Top bar
|
// Top bar
|
||||||
|
val hPad = if (isCompact) 12.dp else 24.dp
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
.padding(horizontal = hPad, vertical = if (isCompact) 8.dp else 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = { appState.goHome() }) {
|
IconButton(onClick = { appState.goHome() }, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
|
if (!isCompact) {
|
||||||
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
||||||
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
if (isAutoMeasuring) {
|
if (isAutoMeasuring) {
|
||||||
Row(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(Color.Red.copy(alpha = 0.12f))
|
.background(Color.Red.copy(alpha = 0.12f))
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 6.dp, vertical = 3.dp)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.size(8.dp).clip(CircleShape).background(Color.Red))
|
Text(if (isCompact) "A" else "Auto", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Red)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text("Auto", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = Color.Red)
|
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catheter count
|
// Catheter count
|
||||||
@@ -163,27 +168,26 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(MlPrimary.copy(alpha = 0.1f))
|
.background(MlPrimary.copy(alpha = 0.1f))
|
||||||
.clickable { showAddCatheterDialog = true }
|
.clickable { showAddCatheterDialog = true }
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 6.dp, vertical = 3.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, "Add catheter", tint = MlPrimary, modifier = Modifier.size(14.dp))
|
Icon(Icons.Default.Add, "Add catheter", tint = MlPrimary, modifier = Modifier.size(12.dp))
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
Text("$catheterCount", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
Text("$catheterCount", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
IconButton(onClick = { appState.showHistory() }) {
|
IconButton(onClick = { appState.showHistory() }, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||||
Icon(Icons.Default.History, "History", tint = MlSecondaryText)
|
Icon(Icons.Default.History, "History", tint = MlSecondaryText, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
IconButton(onClick = { showSettings = !showSettings }) {
|
IconButton(onClick = { showSettings = !showSettings }, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||||
Icon(Icons.Default.Settings, "Settings", tint = if (showSettings) MlPrimary else MlSecondaryText)
|
Icon(Icons.Default.Settings, "Settings", tint = if (showSettings) MlPrimary else MlSecondaryText, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery
|
// Battery / Demo
|
||||||
if (isDemoMode) {
|
if (isDemoMode) {
|
||||||
Text("DEMO", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White,
|
Text("DEMO", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = Color.White,
|
||||||
modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 6.dp, vertical = 2.dp))
|
modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 4.dp, vertical = 2.dp))
|
||||||
} else {
|
} else if (!isCompact) {
|
||||||
Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText)
|
Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
@@ -212,10 +216,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = hPad)
|
||||||
.padding(top = 16.dp, bottom = 32.dp),
|
.padding(top = if (isCompact) 8.dp else 16.dp, bottom = if (isCompact) 16.dp else 32.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
verticalArrangement = Arrangement.spacedBy(if (isCompact) 12.dp else 20.dp)
|
||||||
) {
|
) {
|
||||||
// Settings panel
|
// Settings panel
|
||||||
if (showSettings) {
|
if (showSettings) {
|
||||||
@@ -261,15 +265,16 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Donut chart with Bladdy
|
// Donut chart with Bladdy
|
||||||
|
val donutSize = if (isCompact) 180.dp else 240.dp
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
DonutChartView(
|
DonutChartView(
|
||||||
progress = appState.bladderLevel.progress,
|
progress = appState.bladderLevel.progress,
|
||||||
urgency = urgency,
|
urgency = urgency,
|
||||||
lineWidth = 28.dp,
|
lineWidth = if (isCompact) 20.dp else 28.dp,
|
||||||
size = 240.dp
|
size = donutSize
|
||||||
)
|
)
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
BladdyRiveView(urgency = urgency, size = 100.dp)
|
BladdyRiveView(urgency = urgency, size = if (isCompact) 70.dp else 100.dp)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
"${appState.estimatedVolume} ml",
|
"${appState.estimatedVolume} ml",
|
||||||
|
|||||||
+23
-18
@@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Path
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -45,6 +46,9 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
val nirsManager = remember { NirsManager.instance }
|
val nirsManager = remember { NirsManager.instance }
|
||||||
val handler = remember { Handler(Looper.getMainLooper()) }
|
val handler = remember { Handler(Looper.getMainLooper()) }
|
||||||
|
|
||||||
|
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||||
|
val isCompact = screenWidth < 400
|
||||||
|
|
||||||
var selectedTab by remember { mutableIntStateOf(0) }
|
var selectedTab by remember { mutableIntStateOf(0) }
|
||||||
var showClearConfirm by remember { mutableStateOf(false) }
|
var showClearConfirm by remember { mutableStateOf(false) }
|
||||||
var isToggling by remember { mutableStateOf(false) }
|
var isToggling by remember { mutableStateOf(false) }
|
||||||
@@ -227,28 +231,29 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
.background(Color.White)
|
.background(Color.White)
|
||||||
) {
|
) {
|
||||||
// Top bar
|
// Top bar
|
||||||
|
val hPad = if (isCompact) 12.dp else 24.dp
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = hPad, vertical = if (isCompact) 8.dp else 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
nirsManager.reset()
|
nirsManager.reset()
|
||||||
appState.goHome()
|
appState.goHome()
|
||||||
}) {
|
}, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier.padding(start = 8.dp)) {
|
Column(modifier = Modifier.padding(start = if (isCompact) 4.dp else 8.dp)) {
|
||||||
Text("Vivamyo", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
Text("Vivamyo", fontSize = if (isCompact) 16.sp else 20.sp, fontWeight = FontWeight.Bold)
|
||||||
Text("NIRS Monitoring", fontSize = 13.sp, color = MlSecondaryText)
|
if (!isCompact) Text("NIRS Monitoring", fontSize = 13.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
if (isDemoMode) {
|
if (isDemoMode) {
|
||||||
Text("DEMO", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.White,
|
Text("DEMO", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = Color.White,
|
||||||
modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 6.dp, vertical = 2.dp))
|
modifier = Modifier.clip(RoundedCornerShape(4.dp)).background(Color(0xFFFF9500)).padding(horizontal = 4.dp, vertical = 2.dp))
|
||||||
} else {
|
} else {
|
||||||
Text("$batteryLvl%", fontSize = 13.sp, color = MlSecondaryText)
|
Text("$batteryLvl%", fontSize = if (isCompact) 11.sp else 13.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(if (isCompact) 4.dp else 8.dp))
|
||||||
Box(modifier = Modifier.size(10.dp).clip(CircleShape).background(if (isDemoMode || isConnected) Color.Green else Color.Red))
|
Box(modifier = Modifier.size(10.dp).clip(CircleShape).background(if (isDemoMode || isConnected) Color.Green else Color.Red))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,27 +270,27 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tab picker
|
// Tab picker
|
||||||
TabRow(selectedTabIndex = selectedTab, modifier = Modifier.padding(horizontal = 24.dp)) {
|
TabRow(selectedTabIndex = selectedTab, modifier = Modifier.padding(horizontal = hPad)) {
|
||||||
Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }) { Text("SpO2", modifier = Modifier.padding(12.dp)) }
|
Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }) { Text("SpO2", modifier = Modifier.padding(if (isCompact) 8.dp else 12.dp)) }
|
||||||
Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }) { Text("Oxy/Deoxy", modifier = Modifier.padding(12.dp)) }
|
Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }) { Text("Oxy/Deoxy", modifier = Modifier.padding(if (isCompact) 8.dp else 12.dp)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
.padding(horizontal = hPad, vertical = if (isCompact) 8.dp else 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(if (isCompact) 10.dp else 16.dp)
|
||||||
) {
|
) {
|
||||||
if (selectedTab == 0) {
|
if (selectedTab == 0) {
|
||||||
// SpO2 tab
|
// SpO2 tab
|
||||||
BladdyIdleRiveView(
|
BladdyIdleRiveView(
|
||||||
size = 120.dp,
|
size = if (isCompact) 80.dp else 120.dp,
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(String.format("%.1f", currentOxy.stO2), fontSize = 56.sp, fontWeight = FontWeight.Bold, color = stO2Color)
|
Text(String.format("%.1f", currentOxy.stO2), fontSize = if (isCompact) 40.sp else 56.sp, fontWeight = FontWeight.Bold, color = stO2Color)
|
||||||
Text("% StO2", fontSize = 18.sp, color = MlSecondaryText)
|
Text("% StO2", fontSize = 18.sp, color = MlSecondaryText)
|
||||||
Text(stO2StatusText, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = stO2Color,
|
Text(stO2StatusText, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = stO2Color,
|
||||||
modifier = Modifier.clip(RoundedCornerShape(8.dp)).background(stO2Color.copy(alpha = 0.15f)).padding(horizontal = 12.dp, vertical = 4.dp))
|
modifier = Modifier.clip(RoundedCornerShape(8.dp)).background(stO2Color.copy(alpha = 0.15f)).padding(horizontal = 12.dp, vertical = 4.dp))
|
||||||
@@ -320,7 +325,7 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
|
|
||||||
// Control bar
|
// Control bar
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 24.dp).padding(bottom = 32.dp),
|
modifier = Modifier.padding(horizontal = hPad).padding(bottom = if (isCompact) 16.dp else 32.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
|
|||||||
Reference in New Issue
Block a user