fix(ui): status bar layout and icons for Map, Dashboard, Manage

- Dashboard: apply background before statusBarsPadding so gradient extends under status bar
- Map: add statusBarsPadding to layer selector so it no longer overlaps status bar
- NavGraph: set white status bar icons (isAppearanceLightStatusBars=false) on main tab screens

Made-with: Cursor
This commit is contained in:
2026-03-04 20:25:06 +01:00
parent e59e5aa060
commit c9c7cedd55
3 changed files with 124 additions and 63 deletions

View File

@@ -7,27 +7,28 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import org.db3.airmq.R
import org.db3.airmq.features.common.MockScreenScaffold
import org.db3.airmq.features.common.metric.MetricGaugeRow
import org.db3.airmq.features.common.metric.SensorType
import org.db3.airmq.features.common.ScreenAction
import org.db3.airmq.features.common.chart.AirMQChart
import org.db3.airmq.features.common.chart.ChartConfig
import org.db3.airmq.features.common.chart.ChartDataset
import org.db3.airmq.features.common.chart.generateSineWaveData
import org.db3.airmq.ui.theme.ChartBackground
import org.db3.airmq.ui.theme.ChartFill
import org.db3.airmq.features.common.metric.MetricGaugePager
import org.db3.airmq.features.common.metric.SensorType
import org.db3.airmq.ui.theme.AirMQTheme
import org.db3.airmq.ui.theme.LegacyNavGradientEnd
import org.db3.airmq.ui.theme.LegacyNavGradientStart
@@ -38,57 +39,105 @@ fun DashboardScreen(
onOpenCity: () -> Unit,
onOpenDevice: () -> Unit,
onOpenNews: () -> Unit,
onOpenWidgetConstructor: () -> Unit
onOpenWidgetConstructor: () -> Unit,
viewModel: DashboardViewModel = hiltViewModel()
) {
MockScreenScaffold(
title = stringResource(id = R.string.title_dashboard),
subtitle = stringResource(id = R.string.dashboard_subtitle),
content = {
var selectedSensor by remember { mutableStateOf<SensorType?>(SensorType.DUST) }
Column(
modifier = Modifier
.fillMaxWidth()
.background(
Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
) {
MetricGaugeRow(
selectedSensor = selectedSensor,
values = mapOf(
SensorType.DUST to 6f,
SensorType.RADIOACTIVITY to 0f,
SensorType.TEMPERATURE to 3f
),
onGaugeSelected = { selectedSensor = it },
modifier = Modifier.padding(vertical = 8.dp)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(144.dp)
) {
AirMQChart(
data = ChartDataset.Single(generateSineWaveData()),
config = ChartConfig(
lineColor = Color.White,
fillColor = ChartFill,
backgroundColor = ChartBackground,
labelColor = Color.White,
leftTimeLabel = stringResource(R.string.text_yesterday),
rightTimeLabel = stringResource(R.string.text_now),
unit = "°C"
),
sensorType = "sensor_temperature",
modifier = Modifier.fillMaxSize()
)
}
}
},
actions = listOf(
ScreenAction(stringResource(id = R.string.dashboard_open_news), onOpenNews),
ScreenAction(stringResource(id = R.string.manage_open_widget_constructor), onOpenWidgetConstructor)
)
val state by viewModel.uiState.collectAsState()
DashboardContent(
state = state,
onEvent = viewModel::onEvent
)
}
@Composable
internal fun DashboardContent(
state: DashboardScreenContract.State,
onEvent: (DashboardScreenContract.Event) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(
Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.statusBarsPadding()
) {
CitySelector(city = state.city, modifier = Modifier.padding(top = 12.dp))
MetricGaugePager(
selectedSensor = state.selectedSensor,
values = state.gaugeValues,
currentPage = state.currentPage,
onGaugeSelected = { onEvent(DashboardScreenContract.Event.GaugeSelected(it)) },
onPageChanged = { onEvent(DashboardScreenContract.Event.PageChanged(it)) },
modifier = Modifier.fillMaxWidth()
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(144.dp)
) {
AirMQChart(
data = state.chartData,
config = state.chartConfig.copy(
leftTimeLabel = stringResource(R.string.text_yesterday),
rightTimeLabel = stringResource(R.string.text_now)
),
sensorType = state.selectedSensor.legacyKey,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
@Composable
private fun CitySelector(
city: String,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(44.dp),
contentAlignment = Alignment.Center
) {
Text(
text = city,
color = Color.White,
fontSize = 22.sp,
fontWeight = FontWeight.Medium
)
}
}
@Preview(showBackground = true, name = "Dashboard dust selected")
@Composable
private fun PreviewDashboardDust() {
AirMQTheme {
DashboardContent(
state = DashboardScreenContract.previewState(selectedSensor = SensorType.DUST),
onEvent = {}
)
}
}
@Preview(showBackground = true, name = "Dashboard page 2 humidity")
@Composable
private fun PreviewDashboardPage2() {
AirMQTheme {
DashboardContent(
state = DashboardScreenContract.previewState(
selectedSensor = SensorType.HUMIDITY,
currentPage = 1
),
onEvent = {}
)
}
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -116,6 +117,7 @@ private fun MapScreenContent(
},
modifier = Modifier
.align(Alignment.TopEnd)
.statusBarsPadding()
.padding(top = 20.dp, end = 16.dp)
)

View File

@@ -1,11 +1,13 @@
package org.db3.airmq.features.navigation
import android.app.Activity
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
@@ -13,6 +15,7 @@ import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
@@ -25,6 +28,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.core.view.WindowCompat
import org.db3.airmq.R
import org.db3.airmq.features.city.CityScreen
import org.db3.airmq.features.constructor.ChartConstructorScreen
@@ -64,6 +68,12 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) {
val currentRoute = navBackStackEntry?.destination?.route
val showBottomBar = currentRoute in tabRoutes
val view = LocalView.current
SideEffect {
val window = (view.context as? Activity)?.window ?: return@SideEffect
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !showBottomBar
}
Scaffold(
modifier = modifier,
contentWindowInsets = WindowInsets(0, 0, 0, 0),