From 7c001633040d46341381d9c5e8feb7e530393e18 Mon Sep 17 00:00:00 2001 From: beetzung Date: Sun, 1 Mar 2026 19:02:13 +0100 Subject: [PATCH] Refine Compose manage UI parity and button behavior. Apply legacy-accurate manage header/CTA styling, add reusable button icon support with previews, and include related map/navigation polish updates in this working tree. Made-with: Cursor --- .../{AirMqButtons.kt => AirMQButtons.kt} | 182 +++++++++++++++--- .../features/common/MockScreenScaffold.kt | 4 +- .../db3/airmq/features/manage/ManageScreen.kt | 163 ++++++++++------ .../db3/airmq/features/map/MapUiComponents.kt | 24 +-- .../db3/airmq/features/map/MapViewModel.kt | 24 ++- .../features/navigation/AirMQNavGraph.kt | 2 + app/src/main/res/drawable/ic_account.xml | 10 + app/src/main/res/drawable/ic_settings.xml | 12 ++ .../res/drawable/placeholder_avatar_round.xml | 12 ++ 9 files changed, 324 insertions(+), 109 deletions(-) rename app/src/main/kotlin/org/db3/airmq/features/common/{AirMqButtons.kt => AirMQButtons.kt} (50%) create mode 100644 app/src/main/res/drawable/ic_account.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/placeholder_avatar_round.xml diff --git a/app/src/main/kotlin/org/db3/airmq/features/common/AirMqButtons.kt b/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt similarity index 50% rename from app/src/main/kotlin/org/db3/airmq/features/common/AirMqButtons.kt rename to app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt index 04bed27..90d5064 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/common/AirMqButtons.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/common/AirMQButtons.kt @@ -5,12 +5,21 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.BorderStroke import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -22,8 +31,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import org.db3.airmq.R +import org.db3.airmq.ui.theme.AirMQTheme import org.db3.airmq.ui.theme.LegacyButtonContained import org.db3.airmq.ui.theme.LegacyButtonGradientEnd import org.db3.airmq.ui.theme.LegacyButtonGradientStart @@ -32,7 +46,7 @@ import org.db3.airmq.ui.theme.LegacyButtonOnOutlined import org.db3.airmq.ui.theme.LegacyButtonOnText import org.db3.airmq.ui.theme.LegacyOutlineLight -enum class AirMqButtonStyle { +enum class AirMQButtonStyle { Contained, Outlined, Text, @@ -40,52 +54,58 @@ enum class AirMqButtonStyle { } private val LegacyButtonShape = RoundedCornerShape(18.dp) -private val LegacyButtonHeight = 36.dp +private val LegacyButtonHeight = 48.dp private val LegacyDisabledContainer = Color(0xFFE0E0E0) private val LegacyDisabledContent = Color(0x61000000) @Composable -fun AirMqButton( +fun AirMQButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - style: AirMqButtonStyle = AirMqButtonStyle.Contained + style: AirMQButtonStyle = AirMQButtonStyle.Contained, + leadingIconRes: Int? = null ) { when (style) { - AirMqButtonStyle.Contained -> AirMqContainedButton( + AirMQButtonStyle.Contained -> AirMQContainedButton( text = text, onClick = onClick, modifier = modifier, - enabled = enabled + enabled = enabled, + leadingIconRes = leadingIconRes ) - AirMqButtonStyle.Outlined -> AirMqOutlinedButton( + AirMQButtonStyle.Outlined -> AirMQOutlinedButton( text = text, onClick = onClick, modifier = modifier, - enabled = enabled + enabled = enabled, + leadingIconRes = leadingIconRes ) - AirMqButtonStyle.Text -> AirMqTextButton( + AirMQButtonStyle.Text -> AirMQTextButton( text = text, onClick = onClick, modifier = modifier, - enabled = enabled + enabled = enabled, + leadingIconRes = leadingIconRes ) - AirMqButtonStyle.Gradient -> AirMqGradientButton( + AirMQButtonStyle.Gradient -> AirMQGradientButton( text = text, onClick = onClick, modifier = modifier, - enabled = enabled + enabled = enabled, + leadingIconRes = leadingIconRes ) } } @Composable -fun AirMqContainedButton( +fun AirMQContainedButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true + enabled: Boolean = true, + leadingIconRes: Int? = null ) { Button( onClick = onClick, @@ -99,16 +119,21 @@ fun AirMqContainedButton( disabledContentColor = LegacyDisabledContent ) ) { - Text(text = text, fontWeight = FontWeight.Medium) + AirMQButtonLabel( + text = text, + leadingIconRes = leadingIconRes, + iconTint = if (enabled) LegacyButtonOnContained else LegacyDisabledContent + ) } } @Composable -fun AirMqOutlinedButton( +fun AirMQOutlinedButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true + enabled: Boolean = true, + leadingIconRes: Int? = null ) { OutlinedButton( onClick = onClick, @@ -124,16 +149,21 @@ fun AirMqOutlinedButton( disabledContentColor = LegacyDisabledContent ) ) { - Text(text = text, fontWeight = FontWeight.Medium) + AirMQButtonLabel( + text = text, + leadingIconRes = leadingIconRes, + iconTint = if (enabled) LegacyButtonOnOutlined else LegacyDisabledContent + ) } } @Composable -fun AirMqTextButton( +fun AirMQTextButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true + enabled: Boolean = true, + leadingIconRes: Int? = null ) { TextButton( onClick = onClick, @@ -145,16 +175,21 @@ fun AirMqTextButton( disabledContentColor = LegacyDisabledContent ) ) { - Text(text = text, fontWeight = FontWeight.Medium) + AirMQButtonLabel( + text = text, + leadingIconRes = leadingIconRes, + iconTint = if (enabled) LegacyButtonOnText else LegacyDisabledContent + ) } } @Composable -fun AirMqGradientButton( +fun AirMQGradientButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true + enabled: Boolean = true, + leadingIconRes: Int? = null ) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() @@ -183,6 +218,7 @@ fun AirMqGradientButton( Box( modifier = Modifier .fillMaxWidth() + .heightIn(min = LegacyButtonHeight) .clip(LegacyButtonShape) .background( brush = Brush.horizontalGradient( @@ -192,10 +228,104 @@ fun AirMqGradientButton( .background(overlay), contentAlignment = Alignment.Center ) { - Text( + AirMQButtonLabel( text = text, - fontWeight = FontWeight.Medium, - color = if (enabled) LegacyButtonOnContained else LegacyDisabledContent + leadingIconRes = leadingIconRes, + iconTint = if (enabled) LegacyButtonOnContained else LegacyDisabledContent, + textColor = if (enabled) LegacyButtonOnContained else LegacyDisabledContent + ) + } + } +} + +@Composable +private fun AirMQButtonLabel( + text: String, + leadingIconRes: Int?, + iconTint: Color, + textColor: Color = Color.Unspecified +) { + Row(verticalAlignment = Alignment.CenterVertically) { + if (leadingIconRes != null) { + Icon( + painter = painterResource(id = leadingIconRes), + contentDescription = null, + tint = iconTint, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + } + Text( + text = text.uppercase(), + fontWeight = FontWeight.Medium, + color = textColor, + textAlign = TextAlign.Start + ) + } +} + +@Preview(name = "Buttons - All Styles", showBackground = true) +@Composable +private fun AirMQButtonsPreviewAllStyles() { + AirMQTheme { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + AirMQButton( + text = "Contained", + onClick = {}, + style = AirMQButtonStyle.Contained, + modifier = Modifier.fillMaxWidth() + ) + AirMQButton( + text = "Outlined", + onClick = {}, + style = AirMQButtonStyle.Outlined, + modifier = Modifier.fillMaxWidth() + ) + AirMQButton( + text = "Text", + onClick = {}, + style = AirMQButtonStyle.Text, + modifier = Modifier.fillMaxWidth() + ) + AirMQButton( + text = "Gradient", + onClick = {}, + style = AirMQButtonStyle.Gradient, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Preview(name = "Buttons - Gradient With Icon", showBackground = true) +@Composable +private fun AirMQButtonsPreviewGradientWithIcon() { + AirMQTheme { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + AirMQButton( + text = "Sign In", + onClick = {}, + style = AirMQButtonStyle.Gradient, + leadingIconRes = R.drawable.ic_account, + modifier = Modifier.fillMaxWidth() + ) + AirMQButton( + text = "Sign In", + onClick = {}, + style = AirMQButtonStyle.Gradient, + leadingIconRes = R.drawable.ic_account, + enabled = false, + modifier = Modifier.fillMaxWidth() ) } } diff --git a/app/src/main/kotlin/org/db3/airmq/features/common/MockScreenScaffold.kt b/app/src/main/kotlin/org/db3/airmq/features/common/MockScreenScaffold.kt index 7e0917e..60fdd99 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/common/MockScreenScaffold.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/common/MockScreenScaffold.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.unit.dp data class ScreenAction( val label: String, val onClick: () -> Unit, - val style: AirMqButtonStyle = AirMqButtonStyle.Contained + val style: AirMQButtonStyle = AirMQButtonStyle.Contained ) @Composable @@ -61,7 +61,7 @@ private fun ScreenContent( } content?.invoke() actions.forEach { action -> - AirMqButton( + AirMQButton( text = action.label, onClick = action.onClick, style = action.style, diff --git a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt index fba7fc5..7b4130e 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/manage/ManageScreen.kt @@ -1,6 +1,7 @@ package org.db3.airmq.features.manage import androidx.compose.foundation.background +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -10,12 +11,17 @@ 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.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -27,6 +33,7 @@ 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.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -34,8 +41,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.flow.collectLatest import org.db3.airmq.R -import org.db3.airmq.features.common.AirMqButton -import org.db3.airmq.features.common.AirMqButtonStyle +import org.db3.airmq.features.common.AirMQButton +import org.db3.airmq.features.common.AirMQButtonStyle import org.db3.airmq.features.manage.ManageScreenContract.Action import org.db3.airmq.features.manage.ManageScreenContract.DeviceItem import org.db3.airmq.features.manage.ManageScreenContract.Event @@ -92,10 +99,12 @@ private fun ManageScreenContent( ProfileHeader( name = uiState.userName, email = uiState.userEmail, + isAnonymous = uiState.userMode == UserMode.ANONYMOUS, onSettingsClick = { onEvent(Event.SettingsClicked) } ) when (uiState.userMode) { UserMode.ANONYMOUS -> AnonymousContent( + modifier = Modifier.weight(1f), devicesLabel = uiState.devicesLabel, onSignIn = { onEvent(Event.SignInClicked) } ) @@ -107,22 +116,24 @@ private fun ManageScreenContent( onOpenLocation = { onEvent(Event.DeviceLocationClicked(it)) } ) } - Spacer(modifier = Modifier.height(8.dp)) - AirMqButton( - text = stringResource(id = R.string.manage_open_widget_constructor), - onClick = onOpenWidgetConstructor, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - ) - AirMqButton( - text = stringResource(id = R.string.back_to_dashboard), - onClick = onBackToDashboard, - style = AirMqButtonStyle.Outlined, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) - ) + if (uiState.userMode == UserMode.AUTHORIZED) { + Spacer(modifier = Modifier.height(8.dp)) + AirMQButton( + text = stringResource(id = R.string.manage_open_widget_constructor), + onClick = onOpenWidgetConstructor, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + AirMQButton( + text = stringResource(id = R.string.back_to_dashboard), + onClick = onBackToDashboard, + style = AirMQButtonStyle.Outlined, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } } } } @@ -131,6 +142,7 @@ private fun ManageScreenContent( private fun ProfileHeader( name: String, email: String, + isAnonymous: Boolean, onSettingsClick: () -> Unit ) { Column( @@ -141,39 +153,44 @@ private fun ProfileHeader( colors = listOf(LegacyNavGradientEnd, LegacyNavGradientStart) ) ) - .padding(16.dp) + .statusBarsPadding() + .padding(horizontal = 16.dp) ) { Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.Top ) { - Text( - text = stringResource(id = R.string.title_manage), - style = MaterialTheme.typography.headlineSmall, - color = Color.White - ) - AirMqButton( - text = stringResource(id = R.string.title_settings), - onClick = onSettingsClick, - style = AirMqButtonStyle.Text - ) - } - Spacer(modifier = Modifier.height(12.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = Modifier - .background(color = Color.White.copy(alpha = 0.25f), shape = CircleShape) - .padding(horizontal = 14.dp, vertical = 10.dp) - ) { - Text( - text = name.firstOrNull()?.uppercase() ?: "A", - color = Color.White, - style = MaterialTheme.typography.titleMedium + if (isAnonymous) { + Image( + painter = painterResource(id = R.drawable.placeholder_avatar_round), + contentDescription = stringResource(id = R.string.content_desc_user_pic), + modifier = Modifier + .padding(top = 24.dp) + .size(96.dp) ) + } else { + Box( + modifier = Modifier + .padding(top = 24.dp) + .size(96.dp) + .background(color = Color.White.copy(alpha = 0.25f), shape = CircleShape), + contentAlignment = Alignment.Center + ) { + Text( + text = name.firstOrNull()?.uppercase() ?: "A", + color = Color.White, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Medium + ) + } } - Spacer(modifier = Modifier.padding(horizontal = 8.dp)) - Column { + Spacer(modifier = Modifier.width(12.dp)) + Column( + modifier = Modifier + .weight(1f) + .padding(top = 24.dp) + ) { Text( text = name, color = Color.White, @@ -186,31 +203,50 @@ private fun ProfileHeader( style = MaterialTheme.typography.bodyMedium ) } + IconButton( + onClick = onSettingsClick, + modifier = Modifier + .padding(top = 24.dp) + .size(36.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_settings), + contentDescription = stringResource(id = R.string.content_settings), + tint = Color.White + ) + } } + Spacer(modifier = Modifier.height(24.dp)) } } @Composable private fun AnonymousContent( + modifier: Modifier = Modifier, devicesLabel: String, onSignIn: () -> Unit ) { - Column( - modifier = Modifier + Box( + modifier = modifier .fillMaxWidth() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + .padding(horizontal = 16.dp, vertical = 12.dp) ) { Text( text = devicesLabel, - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Light), + color = Color(0xFFBDBDBD), + modifier = Modifier.align(Alignment.Center) ) - Spacer(modifier = Modifier.height(16.dp)) - AirMqButton( + AirMQButton( text = stringResource(id = R.string.button_sign_in), onClick = onSignIn, - style = AirMqButtonStyle.Gradient, - modifier = Modifier.fillMaxWidth() + style = AirMQButtonStyle.Gradient, + leadingIconRes = R.drawable.ic_account, + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(0.46f) + .height(48.dp) + .padding(bottom = 16.dp) ) } } @@ -230,10 +266,10 @@ private fun AuthorizedContent( ) { Text(text = devicesLabel, style = MaterialTheme.typography.titleMedium) Spacer(modifier = Modifier.height(12.dp)) - AirMqButton( + AirMQButton( text = stringResource(id = R.string.button_setup), onClick = onOpenSetup, - style = AirMqButtonStyle.Gradient, + style = AirMQButtonStyle.Gradient, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(12.dp)) @@ -273,20 +309,20 @@ private fun DeviceRow( ) Spacer(modifier = Modifier.height(8.dp)) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - AirMqButton( + AirMQButton( text = stringResource(id = R.string.button_open), onClick = onOpenDevice, - style = AirMqButtonStyle.Contained, + style = AirMQButtonStyle.Contained, modifier = Modifier.weight(1f) ) - AirMqButton( + AirMQButton( text = if (item.hasLocation) { stringResource(id = R.string.button_view_on_map) } else { stringResource(id = R.string.manage_set_location) }, onClick = onOpenLocation, - style = AirMqButtonStyle.Outlined, + style = AirMQButtonStyle.Outlined, modifier = Modifier.weight(1f) ) } @@ -300,7 +336,10 @@ private fun ManageScreenAnonymousPreview() { AirMQTheme { ManageScreenContent( uiState = State( - userMode = UserMode.ANONYMOUS + userMode = UserMode.ANONYMOUS, + userName = "Anonymous user", + userEmail = "Your preferences are not being synced, please sign in", + devicesLabel = "Sign in to add devices" ), onEvent = {}, onOpenWidgetConstructor = {}, diff --git a/app/src/main/kotlin/org/db3/airmq/features/map/MapUiComponents.kt b/app/src/main/kotlin/org/db3/airmq/features/map/MapUiComponents.kt index e5f42a4..727d272 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/map/MapUiComponents.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/map/MapUiComponents.kt @@ -41,8 +41,8 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.db3.airmq.R -import org.db3.airmq.features.common.AirMqButton -import org.db3.airmq.features.common.AirMqButtonStyle +import org.db3.airmq.features.common.AirMQButton +import org.db3.airmq.features.common.AirMQButtonStyle import org.db3.airmq.features.map.MapScreenContract.DevicePanelState import org.db3.airmq.features.map.MapScreenContract.DeviceSensorType import org.db3.airmq.features.map.MapScreenContract.SearchResult @@ -279,10 +279,10 @@ fun MapSearchOverlay( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically ) { - AirMqButton( + AirMQButton( text = stringResource(id = R.string.content_back), onClick = onClose, - style = AirMqButtonStyle.Outlined + style = AirMQButtonStyle.Outlined ) OutlinedTextField( value = query, @@ -356,10 +356,10 @@ fun MapDevicePanel( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - AirMqButton( + AirMQButton( text = stringResource(id = R.string.button_close), onClick = onClose, - style = AirMqButtonStyle.Text + style = AirMQButtonStyle.Text ) Column( modifier = Modifier.weight(1f), @@ -372,10 +372,10 @@ fun MapDevicePanel( color = Color.Gray ) } - AirMqButton( + AirMQButton( text = stringResource(id = R.string.button_open), onClick = onOpenDevice, - style = AirMqButtonStyle.Outlined + style = AirMQButtonStyle.Outlined ) } @@ -386,19 +386,19 @@ fun MapDevicePanel( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - AirMqButton( + AirMQButton( text = stringResource(id = R.string.map_arrow_left), onClick = onDateBack, - style = AirMqButtonStyle.Text + style = AirMQButtonStyle.Text ) Text( text = data.displayedDateRange, style = MaterialTheme.typography.bodyMedium ) - AirMqButton( + AirMQButton( text = stringResource(id = R.string.map_arrow_right), onClick = onDateForward, - style = AirMqButtonStyle.Text + style = AirMQButtonStyle.Text ) } diff --git a/app/src/main/kotlin/org/db3/airmq/features/map/MapViewModel.kt b/app/src/main/kotlin/org/db3/airmq/features/map/MapViewModel.kt index 0d2c2d8..bdff7d2 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/map/MapViewModel.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/map/MapViewModel.kt @@ -42,6 +42,8 @@ class MapViewModel @Inject constructor( private val _actions = MutableSharedFlow(extraBufferCapacity = 1) val actions: SharedFlow = _actions.asSharedFlow() + private var showOfflineDevices = false + init { refreshMapItems() } @@ -158,14 +160,15 @@ class MapViewModel @Inject constructor( _uiState.value = _uiState.value.copy(isLoading = true) viewModelScope.launch(Dispatchers.IO) { val result = runCatching { - val showOfflineDevices = settingsService.getOfflineDevicesVisible() + showOfflineDevices = settingsService.getOfflineDevicesVisible() mapService.fetchMapItems(showOfflineDevices = showOfflineDevices) } _uiState.value = result.fold( onSuccess = { items -> domainItems = items val searchPanelState = _uiState.value.searchPanelState - val markers = items.toMarkers(_uiState.value.selectedTopSensor) + val selectedSensorType = _uiState.value.selectedTopSensor + val markers = items.toMarkers(selectedSensorType) _uiState.value.copy( isLoading = false, items = markers, @@ -223,7 +226,17 @@ class MapViewModel @Inject constructor( } private fun List.toMarkers(sensorType: SensorType): List { - return map { item -> + return mapNotNull { item -> + val value = when (sensorType) { + SensorType.DUST -> item.dustValue + SensorType.RADIOACTIVITY -> item.radioactivityValue + } + + // Return null if device is offline or value is missing + if (!showOfflineDevices && (!item.isOnline || value == null )) { + return@mapNotNull null + } + MapMarker( id = item.id, title = item.title, @@ -232,10 +245,7 @@ class MapViewModel @Inject constructor( longitude = item.longitude, isOnline = item.isOnline, sensorType = sensorType, - value = when (sensorType) { - SensorType.DUST -> item.dustValue - SensorType.RADIOACTIVITY -> item.radioactivityValue - }, + value = value, isOwned = false // TODO: derive from authenticated user when ownership/auth is implemented. ) } diff --git a/app/src/main/kotlin/org/db3/airmq/features/navigation/AirMQNavGraph.kt b/app/src/main/kotlin/org/db3/airmq/features/navigation/AirMQNavGraph.kt index 74b0023..292bed5 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/navigation/AirMQNavGraph.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/navigation/AirMQNavGraph.kt @@ -1,6 +1,7 @@ package org.db3.airmq.features.navigation 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 @@ -64,6 +65,7 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) { Scaffold( modifier = modifier, + contentWindowInsets = WindowInsets(0, 0, 0, 0), bottomBar = { if (showBottomBar) { Box( diff --git a/app/src/main/res/drawable/ic_account.xml b/app/src/main/res/drawable/ic_account.xml new file mode 100644 index 0000000..dd85bff --- /dev/null +++ b/app/src/main/res/drawable/ic_account.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..62d20ac --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/placeholder_avatar_round.xml b/app/src/main/res/drawable/placeholder_avatar_round.xml new file mode 100644 index 0000000..6a41cd3 --- /dev/null +++ b/app/src/main/res/drawable/placeholder_avatar_round.xml @@ -0,0 +1,12 @@ + + + +