diff --git a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt index e993bdd..77bfcc8 100644 --- a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt @@ -47,12 +47,17 @@ import androidx.compose.material.icons.filled.DriveEta import androidx.compose.material.icons.filled.Terrain import androidx.compose.material.icons.filled.Thermostat import androidx.compose.material.icons.filled.Timeline +import androidx.compose.material.icons.filled.WbSunny import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Check import com.matedroid.ui.icons.CustomIcons import androidx.compose.material.icons.filled.Timer import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.AlertDialog +import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.rememberTooltipState import androidx.compose.material3.TextButton import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -82,7 +87,9 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import kotlinx.coroutines.launch import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asImageBitmap @@ -625,6 +632,41 @@ private fun CarImage( } } +/** + * An icon with a tooltip that appears on tap + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun StatusIcon( + icon: ImageVector, + tooltipText: String, + tint: Color, + modifier: Modifier = Modifier, + iconSize: Int = 18 +) { + val tooltipState = rememberTooltipState(isPersistent = true) + val scope = rememberCoroutineScope() + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(tooltipText) + } + }, + state = tooltipState + ) { + Icon( + imageVector = icon, + contentDescription = tooltipText, + modifier = modifier + .size(iconSize.dp) + .clickable { scope.launch { tooltipState.show() } }, + tint = tint + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun StatusIndicatorsRow( status: CarStatus, @@ -632,99 +674,141 @@ private fun StatusIndicatorsRow( palette: CarColorPalette, modifier: Modifier = Modifier ) { + val isSentryModeActive = status.sentryMode == true + val isClimateOn = status.isClimateOn == true + val isOnline = status.state?.lowercase() == "online" + val isLocked = status.locked == true + Row( modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - // Left side: State and Lock - Row(verticalAlignment = Alignment.CenterVertically) { - // State indicator - icon changes based on state - val stateIcon = when { - status.isCharging || status.pluggedIn == true -> Icons.Filled.PowerSettingsNew - status.state?.lowercase() == "online" -> Icons.Filled.Circle - status.state?.lowercase() in listOf("asleep", "offline", "suspended") -> Icons.Filled.Bedtime - else -> Icons.Filled.Circle + // Left side: Status icons + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Power/state icon - green when online, grey otherwise + StatusIcon( + icon = Icons.Filled.PowerSettingsNew, + tooltipText = status.state?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.unknown), + tint = if (isOnline) StatusSuccess else palette.onSurfaceVariant + ) + + // Lock icon - grey when locked, light red when unlocked + StatusIcon( + icon = if (isLocked) Icons.Filled.Lock else Icons.Filled.LockOpen, + tooltipText = stringResource(if (isLocked) R.string.locked else R.string.unlocked), + tint = if (isLocked) palette.onSurfaceVariant else StatusError.copy(alpha = 0.7f) + ) + + // Sentry mode red dot (if active) + if (isSentryModeActive) { + val sentryTooltipState = rememberTooltipState(isPersistent = true) + val scope = rememberCoroutineScope() + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(stringResource(R.string.sentry_mode_active)) + } + }, + state = sentryTooltipState + ) { + Box( + modifier = Modifier + .size(12.dp) + .background(StatusError, RoundedCornerShape(6.dp)) + .clickable { scope.launch { sentryTooltipState.show() } } + ) + } } - val stateColor = when { - status.isCharging -> StatusSuccess - status.pluggedIn == true -> palette.accent - status.state?.lowercase() == "online" -> StatusSuccess - else -> palette.onSurfaceVariant - } - Icon( - imageVector = stateIcon, - contentDescription = null, - modifier = Modifier.size(if (stateIcon == Icons.Filled.Circle) 10.dp else 16.dp), - tint = stateColor - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = status.state?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.unknown), - style = MaterialTheme.typography.labelMedium, - color = stateColor - ) - Spacer(modifier = Modifier.width(12.dp)) - - // Locked indicator - val isLocked = status.locked == true - Icon( - imageVector = if (isLocked) Icons.Filled.Lock else Icons.Filled.LockOpen, - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = if (isLocked) StatusSuccess else StatusWarning - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = stringResource(if (isLocked) R.string.locked else R.string.unlocked), - style = MaterialTheme.typography.labelMedium, - color = if (isLocked) StatusSuccess else StatusWarning - ) - - // Plug indicator (only when plugged in but not charging - charging already shows state) - if (status.pluggedIn == true && !status.isCharging) { - Spacer(modifier = Modifier.width(8.dp)) - Icon( - imageVector = Icons.Filled.Power, - contentDescription = stringResource(R.string.plugged_in), - modifier = Modifier.size(16.dp), - tint = palette.accent + // Plug icon (grey, if plugged in) + if (status.pluggedIn == true) { + StatusIcon( + icon = Icons.Filled.Power, + tooltipText = stringResource(R.string.plugged_in), + tint = palette.onSurfaceVariant ) } } - // Right side: Temperatures - Row(verticalAlignment = Alignment.CenterVertically) { - // Inside temp - Icon( - imageVector = Icons.Filled.Thermostat, - contentDescription = null, - modifier = Modifier.size(14.dp), - tint = palette.onSurfaceVariant - ) - Spacer(modifier = Modifier.width(2.dp)) - Text( - text = status.insideTemp?.let { UnitFormatter.formatTemperature(it, units) } ?: "--", - style = MaterialTheme.typography.labelMedium, - color = palette.onSurfaceVariant - ) + // Right side: Temperature indicators with labels + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val climateTooltip = stringResource(if (isClimateOn) R.string.climate_active else R.string.climate_inactive) + val scope = rememberCoroutineScope() - Spacer(modifier = Modifier.width(8.dp)) + // Outside temp: "Ext:" + val extTooltipState = rememberTooltipState(isPersistent = true) + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { PlainTooltip { Text(climateTooltip) } }, + state = extTooltipState + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable { scope.launch { extTooltipState.show() } } + ) { + Text( + text = stringResource(R.string.temp_ext_label), + style = MaterialTheme.typography.labelMedium, + color = palette.onSurfaceVariant + ) + Spacer(modifier = Modifier.width(2.dp)) + Icon( + imageVector = Icons.Filled.Thermostat, + contentDescription = stringResource(R.string.outside_temp), + modifier = Modifier.size(14.dp), + tint = palette.onSurfaceVariant + ) + Spacer(modifier = Modifier.width(2.dp)) + Text( + text = status.outsideTemp?.let { UnitFormatter.formatTemperature(it, units) } ?: "--", + style = MaterialTheme.typography.labelMedium, + color = palette.onSurfaceVariant + ) + } + } - // Outside temp - Icon( - imageVector = Icons.Filled.Thermostat, - contentDescription = null, - modifier = Modifier.size(14.dp), - tint = palette.accent - ) - Spacer(modifier = Modifier.width(2.dp)) - Text( - text = status.outsideTemp?.let { UnitFormatter.formatTemperature(it, units) } ?: "--", - style = MaterialTheme.typography.labelMedium, - color = palette.accent - ) + // Inside temp: "Int:" (bold and green if climate is on) + val intTooltipState = rememberTooltipState(isPersistent = true) + val intColor = if (isClimateOn) StatusSuccess else palette.onSurfaceVariant + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { PlainTooltip { Text(climateTooltip) } }, + state = intTooltipState + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.clickable { scope.launch { intTooltipState.show() } } + ) { + Text( + text = stringResource(R.string.temp_int_label), + style = MaterialTheme.typography.labelMedium, + fontWeight = if (isClimateOn) FontWeight.Bold else FontWeight.Normal, + color = intColor + ) + Spacer(modifier = Modifier.width(2.dp)) + Icon( + imageVector = Icons.Filled.Thermostat, + contentDescription = stringResource(R.string.inside_temp), + modifier = Modifier.size(14.dp), + tint = intColor + ) + Spacer(modifier = Modifier.width(2.dp)) + Text( + text = status.insideTemp?.let { UnitFormatter.formatTemperature(it, units) } ?: "--", + style = MaterialTheme.typography.labelMedium, + fontWeight = if (isClimateOn) FontWeight.Bold else FontWeight.Normal, + color = intColor + ) + } + } } } } diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 5380a6f..ef82936 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -48,6 +48,30 @@ Connectat + + Conduint + + + Mode sentinella actiu + + + Temperatura interior + + + Temperatura exterior + + + Ext: + + + Int: + + + Clima actiu + + + Clima inactiu + Imatge del cotxe - toca per estadístiques diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 27dd599..6f96e1b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -48,6 +48,30 @@ Conectado + + Conduciendo + + + Modo centinela activo + + + Temperatura interior + + + Temperatura exterior + + + Ext: + + + Int: + + + Clima activo + + + Clima inactivo + Imagen del coche - toca para estadísticas diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index dfc251b..5252368 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -48,6 +48,30 @@ Collegato + + In guida + + + Modalità sentinella attiva + + + Temperatura interna + + + Temperatura esterna + + + Est: + + + Int: + + + Clima attivo + + + Clima inattivo + Immagine auto - tocca per statistiche diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fdf0e97..09a5e20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,30 @@ Plugged in + + Driving + + + Sentry mode active + + + Inside temperature + + + Outside temperature + + + Ext: + + + Int: + + + Climate active + + + Climate inactive + Car image - tap for stats