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
This commit is contained in:
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {},
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ class MapViewModel @Inject constructor(
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _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<MapItem>.toMarkers(sensorType: SensorType): List<MapMarker> {
|
||||
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.
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
10
app/src/main/res/drawable/ic_account.xml
Normal file
10
app/src/main/res/drawable/ic_account.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_settings.xml
Normal file
12
app/src/main/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:tools="http://schemas.android.com/tools"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
|
||||
tools:ignore="VectorPath" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/placeholder_avatar_round.xml
Normal file
12
app/src/main/res/drawable/placeholder_avatar_round.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="96dp"
|
||||
android:height="96dp"
|
||||
android:viewportWidth="96"
|
||||
android:viewportHeight="96">
|
||||
<path
|
||||
android:pathData="M48,96L48,96C21.49,96 0,74.51 0,48v0C0,21.49 21.49,0 48,0h0c26.51,0 48,21.49 48,48v0C96,74.51 74.51,96 48,96z"
|
||||
android:fillColor="#E6E6E6" />
|
||||
<path
|
||||
android:pathData="M46.79,83.76c-5.16,-0.07 -11.1,-1.5 -16.25,-5.39c-2.98,-2.26 -4.96,-5.17 -5.51,-8.96c-0.37,-2.58 0.36,-4.1 2.65,-5.32c0.23,-0.12 0.36,-0.5 0.47,-0.78c0.45,-1.22 0.9,-2.43 1.29,-3.67c0.15,-0.46 0.33,-1.02 0.19,-1.43c-0.66,-2.06 -1.49,-4.06 -2.13,-6.13c-0.99,-3.2 -0.94,-6.48 -0.51,-9.78c0.44,-3.45 0.7,-6.93 1.19,-10.38c0.46,-3.21 0.93,-6.44 1.73,-9.58c0.88,-3.48 3.38,-5.83 6.52,-7.34c7.65,-3.68 15.4,-3.67 23.06,-0.01c4.51,2.15 6.78,5.95 7.34,10.83c0.48,4.15 0.92,8.31 1.42,12.46c0.24,2 0.69,3.97 0.89,5.97c0.37,3.65 -0.27,7.17 -1.65,10.56c-0.26,0.64 -0.42,1.33 -0.72,1.95c-0.64,1.31 -0.99,2.57 0.25,3.75c0.1,0.1 0.17,0.27 0.2,0.42c0.3,1.77 0.98,3.12 2.59,4.3c1.72,1.26 1.32,3.48 0.85,5.41c-1.03,4.25 -3.84,7.09 -7.46,9.22C58.5,82.61 53.38,83.75 46.79,83.76zM45.49,38.67c-3.49,-2.2 -6.81,-4.38 -11,-4.36c-1.49,0 -2.5,1.18 -2.56,2.76c-0.07,1.69 1.08,3.52 2.6,4.2C37.59,42.63 42.66,41.29 45.49,38.67zM50.27,38.81c3.31,2.18 6.45,3.6 10.17,2.73c2.12,-0.49 3.29,-1.93 3.54,-4.06c0.27,-2.3 -1.64,-3.68 -4.32,-3.11c-3.11,0.66 -5.86,2.11 -8.49,3.82C50.94,38.34 50.73,38.5 50.27,38.81zM48.23,58.86c2,-0.09 3.9,-0.78 5.13,-1.85c0.97,-0.84 0.93,-1.6 -0.17,-2.18c-3.46,-1.82 -6.95,-1.83 -10.42,-0.02c-1.13,0.59 -1.21,1.36 -0.24,2.17C44.18,58.34 46.11,58.92 48.23,58.86z"
|
||||
android:fillColor="#808080" />
|
||||
</vector>
|
||||
Reference in New Issue
Block a user