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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.res.stringResource 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.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import org.db3.airmq.R 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.AirMQChart
import org.db3.airmq.features.common.chart.ChartConfig import org.db3.airmq.features.common.metric.MetricGaugePager
import org.db3.airmq.features.common.chart.ChartDataset import org.db3.airmq.features.common.metric.SensorType
import org.db3.airmq.features.common.chart.generateSineWaveData import org.db3.airmq.ui.theme.AirMQTheme
import org.db3.airmq.ui.theme.ChartBackground
import org.db3.airmq.ui.theme.ChartFill
import org.db3.airmq.ui.theme.LegacyNavGradientEnd import org.db3.airmq.ui.theme.LegacyNavGradientEnd
import org.db3.airmq.ui.theme.LegacyNavGradientStart import org.db3.airmq.ui.theme.LegacyNavGradientStart
@@ -38,13 +39,26 @@ fun DashboardScreen(
onOpenCity: () -> Unit, onOpenCity: () -> Unit,
onOpenDevice: () -> Unit, onOpenDevice: () -> Unit,
onOpenNews: () -> Unit, onOpenNews: () -> Unit,
onOpenWidgetConstructor: () -> Unit onOpenWidgetConstructor: () -> Unit,
viewModel: DashboardViewModel = hiltViewModel()
) { ) {
MockScreenScaffold( val state by viewModel.uiState.collectAsState()
title = stringResource(id = R.string.title_dashboard), DashboardContent(
subtitle = stringResource(id = R.string.dashboard_subtitle), state = state,
content = { onEvent = viewModel::onEvent
var selectedSensor by remember { mutableStateOf<SensorType?>(SensorType.DUST) } )
}
@Composable
internal fun DashboardContent(
state: DashboardScreenContract.State,
onEvent: (DashboardScreenContract.Event) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -53,16 +67,16 @@ fun DashboardScreen(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd) colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
) )
) )
.statusBarsPadding()
) { ) {
MetricGaugeRow( CitySelector(city = state.city, modifier = Modifier.padding(top = 12.dp))
selectedSensor = selectedSensor, MetricGaugePager(
values = mapOf( selectedSensor = state.selectedSensor,
SensorType.DUST to 6f, values = state.gaugeValues,
SensorType.RADIOACTIVITY to 0f, currentPage = state.currentPage,
SensorType.TEMPERATURE to 3f onGaugeSelected = { onEvent(DashboardScreenContract.Event.GaugeSelected(it)) },
), onPageChanged = { onEvent(DashboardScreenContract.Event.PageChanged(it)) },
onGaugeSelected = { selectedSensor = it }, modifier = Modifier.fillMaxWidth()
modifier = Modifier.padding(vertical = 8.dp)
) )
Box( Box(
modifier = Modifier modifier = Modifier
@@ -70,25 +84,60 @@ fun DashboardScreen(
.height(144.dp) .height(144.dp)
) { ) {
AirMQChart( AirMQChart(
data = ChartDataset.Single(generateSineWaveData()), data = state.chartData,
config = ChartConfig( config = state.chartConfig.copy(
lineColor = Color.White,
fillColor = ChartFill,
backgroundColor = ChartBackground,
labelColor = Color.White,
leftTimeLabel = stringResource(R.string.text_yesterday), leftTimeLabel = stringResource(R.string.text_yesterday),
rightTimeLabel = stringResource(R.string.text_now), rightTimeLabel = stringResource(R.string.text_now)
unit = "°C"
), ),
sensorType = "sensor_temperature", sensorType = state.selectedSensor.legacyKey,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
} }
} }
}, }
actions = listOf( }
ScreenAction(stringResource(id = R.string.dashboard_open_news), onOpenNews),
ScreenAction(stringResource(id = R.string.manage_open_widget_constructor), onOpenWidgetConstructor) @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.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@@ -116,6 +117,7 @@ private fun MapScreenContent(
}, },
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.statusBarsPadding()
.padding(top = 20.dp, end = 16.dp) .padding(top = 20.dp, end = 16.dp)
) )

View File

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