diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 400638ff8..1c2be4ef4 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -34,16 +34,9 @@ import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.twotone.Chat -import androidx.compose.material.icons.twotone.CloudDone -import androidx.compose.material.icons.twotone.CloudOff -import androidx.compose.material.icons.twotone.CloudUpload -import androidx.compose.material.icons.twotone.Contactless -import androidx.compose.material.icons.twotone.Map -import androidx.compose.material.icons.twotone.People +import androidx.compose.material.icons.rounded.QrCode2 +import androidx.compose.material.icons.rounded.Wifi import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Scaffold @@ -86,6 +79,7 @@ import com.geeksville.mesh.R import com.geeksville.mesh.android.AddNavigationTracking import com.geeksville.mesh.android.BuildUtils.debug import com.geeksville.mesh.android.setAttributes +import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.Node @@ -106,10 +100,14 @@ import com.geeksville.mesh.ui.common.components.MainMenuAction import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog import com.geeksville.mesh.ui.common.components.SimpleAlertDialog +import com.geeksville.mesh.ui.common.icons.Map +import com.geeksville.mesh.ui.common.icons.MeshtasticIcons +import com.geeksville.mesh.ui.common.icons.Messages +import com.geeksville.mesh.ui.common.icons.Nodes import com.geeksville.mesh.ui.common.theme.StatusColors.StatusBlue import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen -import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed -import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow +import com.geeksville.mesh.ui.connections.DeviceType +import com.geeksville.mesh.ui.connections.components.TopLevelNavIcon import com.geeksville.mesh.ui.node.components.NodeMenuAction import com.geeksville.mesh.ui.sharing.SharedContactDialog import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -119,11 +117,11 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, val route: Route) { - Contacts(R.string.contacts, Icons.AutoMirrored.TwoTone.Chat, ContactsRoutes.ContactsGraph), - Nodes(R.string.nodes, Icons.TwoTone.People, NodesRoutes.NodesGraph), - Map(R.string.map, Icons.TwoTone.Map, MapRoutes.Map), - Channels(R.string.channels, Icons.TwoTone.Contactless, ChannelsRoutes.ChannelsGraph), - Connections(R.string.connections, Icons.TwoTone.CloudOff, ConnectionsRoutes.ConnectionsGraph), + Messages(R.string.contacts, MeshtasticIcons.Messages, ContactsRoutes.ContactsGraph), + Nodes(R.string.nodes, MeshtasticIcons.Nodes, NodesRoutes.NodesGraph), + Map(R.string.map, MeshtasticIcons.Map, MapRoutes.Map), + Share(R.string.bottom_nav_share, Icons.Rounded.QrCode2, ChannelsRoutes.ChannelsGraph), + Connections(R.string.connections, Icons.Rounded.Wifi, ConnectionsRoutes.ConnectionsGraph), ; companion object { @@ -147,6 +145,7 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, fun MainScreen( uIViewModel: UIViewModel = hiltViewModel(), bluetoothViewModel: BluetoothViewModel = hiltViewModel(), + scanModel: BTScanModel = hiltViewModel(), onAction: (MainMenuAction) -> Unit, ) { val navController = rememberNavController() @@ -226,6 +225,9 @@ fun MainScreen( val currentDestination = navController.currentBackStackEntryAsState().value?.destination val topLevelDestination = TopLevelDestination.fromNavDestination(currentDestination) + // State for determining the connection type icon to display + val selectedDevice by scanModel.selectedNotNullFlow.collectAsStateWithLifecycle() + // State for managing the glow animation around the Connections icon var currentGlowColor by remember { mutableStateOf(Color.Transparent) } val animatedGlowAlpha = remember { Animatable(0f) } @@ -272,7 +274,11 @@ fun MainScreen( PlainTooltip { Text( if (isConnectionsRoute) { - connectionState.getTooltipString() + when (connectionState) { + ConnectionState.CONNECTED -> stringResource(R.string.connected) + ConnectionState.DEVICE_SLEEP -> stringResource(R.string.device_sleeping) + ConnectionState.DISCONNECTED -> stringResource(R.string.disconnected) + } } else { stringResource(id = destination.label) }, @@ -313,7 +319,9 @@ fun MainScreen( } else { Modifier } - Box(modifier = iconModifier) { TopLevelNavIcon(destination, connectionState) } + Box(modifier = iconModifier) { + TopLevelNavIcon(destination, connectionState, DeviceType.fromAddress(selectedDevice)) + } } }, selected = isSelected, @@ -380,25 +388,6 @@ fun MainScreen( } } -@Composable -private fun TopLevelNavIcon(destination: TopLevelDestination, connectionState: ConnectionState) { - val iconTint = - when { - destination == TopLevelDestination.Connections -> connectionState.getConnectionColor() - else -> LocalContentColor.current - } - Icon( - imageVector = - if (destination == TopLevelDestination.Connections) { - connectionState.getConnectionIcon() - } else { - destination.icon - }, - contentDescription = stringResource(id = destination.label), - tint = iconTint, - ) -} - @Composable @Suppress("LongMethod", "CyclomaticComplexMethod") private fun VersionChecks(viewModel: UIViewModel) { @@ -483,23 +472,3 @@ private fun VersionChecks(viewModel: UIViewModel) { } } } - -@Composable -private fun ConnectionState.getConnectionColor(): Color = when (this) { - ConnectionState.CONNECTED -> colorScheme.StatusGreen - ConnectionState.DEVICE_SLEEP -> colorScheme.StatusYellow - ConnectionState.DISCONNECTED -> colorScheme.StatusRed -} - -private fun ConnectionState.getConnectionIcon(): ImageVector = when (this) { - ConnectionState.CONNECTED -> Icons.TwoTone.CloudDone - ConnectionState.DEVICE_SLEEP -> Icons.TwoTone.CloudUpload - ConnectionState.DISCONNECTED -> Icons.TwoTone.CloudOff -} - -@Composable -private fun ConnectionState.getTooltipString(): String = when (this) { - ConnectionState.CONNECTED -> stringResource(R.string.connected) - ConnectionState.DEVICE_SLEEP -> stringResource(R.string.device_sleeping) - ConnectionState.DISCONNECTED -> stringResource(R.string.disconnected) -} diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/MaterialBatteryInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/MaterialBatteryInfo.kt index 2ff3b1fc1..39908318f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/MaterialBatteryInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/MaterialBatteryInfo.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import com.geeksville.mesh.ui.common.icons.BatteryEmpty import com.geeksville.mesh.ui.common.icons.BatteryUnknown +import com.geeksville.mesh.ui.common.icons.MeshtasticIcons import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen import com.geeksville.mesh.ui.common.theme.StatusColors.StatusOrange @@ -72,7 +73,7 @@ fun MaterialBatteryInfo(modifier: Modifier = Modifier, level: Int) { } else if (level < 0) { Icon( modifier = Modifier.size(SIZE_ICON.dp), - imageVector = BatteryUnknown, + imageVector = MeshtasticIcons.BatteryUnknown, tint = MaterialTheme.colorScheme.onSurface, contentDescription = null, ) @@ -104,7 +105,7 @@ fun MaterialBatteryInfo(modifier: Modifier = Modifier, level: Int) { size = Size(fillWidth, availableHeight), ) }, - imageVector = BatteryEmpty, + imageVector = MeshtasticIcons.BatteryEmpty, tint = MaterialTheme.colorScheme.onSurface, contentDescription = null, ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Battery.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Battery.kt index 382844980..eb1e99618 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Battery.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Battery.kt @@ -27,9 +27,9 @@ import androidx.compose.ui.unit.dp * This is from Material Symbols. * * @see - * [battery_android_0](https://fonts.google.com/icons?selected=Material+Symbols+Outlined:battery_android_0:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=battery&icon.set=Material+Symbols&icon.size=24&icon.color=%23000000&icon.platform=android) + * [battery_android_0](https://fonts.google.com/icons?icon.query=battery+android+0&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded) */ -val BatteryEmpty: ImageVector +val MeshtasticIcons.BatteryEmpty: ImageVector get() { if (batteryEmpty != null) { return batteryEmpty!! @@ -99,9 +99,9 @@ private var batteryEmpty: ImageVector? = null * This is from Material Symbols. * * @see - * [battery_android_question](https://fonts.google.com/icons?selected=Material+Symbols+Outlined:battery_android_question:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=battery&icon.set=Material+Symbols&icon.size=24&icon.color=%23000000&icon.platform=android) + * [battery_android_question](https://fonts.google.com/icons?icon.query=battery+android+question&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded) */ -val BatteryUnknown: ImageVector +val MeshtasticIcons.BatteryUnknown: ImageVector get() { if (batteryUnknown != null) { return batteryUnknown!! diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Device.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Device.kt new file mode 100644 index 000000000..5ae2b21e1 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Device.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * This is from Material Symbols. + * + * @see + * [router](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:router:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=router&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded) + */ +val MeshtasticIcons.Device: ImageVector + get() { + if (device != null) { + return device!! + } + device = + ImageVector.Builder( + name = "Outlined.Device", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ) + .apply { + path(fill = SolidColor(Color(0xFFE3E3E3))) { + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 520f) + horizontalLineToRelative(400f) + verticalLineToRelative(-120f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 400f) + verticalLineToRelative(120f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 600f) + verticalLineToRelative(160f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(760f, 840f) + lineTo(200f, 840f) + close() + moveTo(200f, 760f) + horizontalLineToRelative(560f) + verticalLineToRelative(-160f) + lineTo(200f, 600f) + verticalLineToRelative(160f) + close() + moveTo(280f, 720f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(320f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(280f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(240f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(280f, 720f) + close() + moveTo(420f, 720f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(460f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(420f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(380f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(420f, 720f) + close() + moveTo(560f, 720f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(600f, 680f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(560f, 640f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(520f, 680f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(560f, 720f) + close() + moveTo(640f, 300f) + quadToRelative(-11f, 0f, -20f, 2f) + reflectiveQuadToRelative(-18f, 6f) + quadToRelative(-16f, 7f, -32.5f, 6f) + reflectiveQuadTo(541f, 301f) + quadToRelative(-12f, -12f, -11.5f, -29f) + reflectiveQuadToRelative(14.5f, -25f) + quadToRelative(21f, -13f, 45.5f, -20f) + reflectiveQuadToRelative(50.5f, -7f) + quadToRelative(27f, 0f, 51f, 7f) + reflectiveQuadToRelative(45f, 20f) + quadToRelative(14f, 8f, 14.5f, 25f) + reflectiveQuadTo(739f, 301f) + quadToRelative(-12f, 12f, -29f, 13f) + reflectiveQuadToRelative(-33f, -6f) + quadToRelative(-8f, -4f, -17.5f, -6f) + reflectiveQuadToRelative(-19.5f, -2f) + close() + moveTo(640f, 160f) + quadToRelative(-39f, 0f, -74.5f, 11.5f) + reflectiveQuadTo(500f, 205f) + quadToRelative(-14f, 10f, -30.5f, 9f) + reflectiveQuadTo(442f, 202f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(13f, -26f) + quadToRelative(41f, -32f, 91f, -50f) + reflectiveQuadToRelative(106f, -18f) + quadToRelative(56f, 0f, 106f, 18f) + reflectiveQuadToRelative(91f, 50f) + quadToRelative(13f, 10f, 13f, 26f) + reflectiveQuadToRelative(-12f, 28f) + quadToRelative(-11f, 11f, -27.5f, 12f) + reflectiveQuadToRelative(-30.5f, -9f) + quadToRelative(-30f, -22f, -65.5f, -33.5f) + reflectiveQuadTo(640f, 160f) + close() + moveTo(200f, 760f) + verticalLineToRelative(-160f) + verticalLineToRelative(160f) + close() + } + } + .build() + + return device!! + } + +private var device: ImageVector? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Map.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Map.kt new file mode 100644 index 000000000..62c3d23eb --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Map.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * This is from Material Symbols. + * + * @see + * [map](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:map:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=map&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded) + */ +val MeshtasticIcons.Map: ImageVector + get() { + if (map != null) { + return map!! + } + map = + ImageVector.Builder( + name = "Outlined.Map", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ) + .apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(574f, 831f) + lineToRelative(-214f, -75f) + lineToRelative(-186f, 72f) + quadToRelative(-10f, 4f, -19.5f, 2.5f) + reflectiveQuadTo(137f, 824f) + quadToRelative(-8f, -5f, -12.5f, -13.5f) + reflectiveQuadTo(120f, 791f) + verticalLineToRelative(-561f) + quadToRelative(0f, -13f, 7.5f, -23f) + reflectiveQuadToRelative(20.5f, -15f) + lineToRelative(186f, -63f) + quadToRelative(6f, -2f, 12.5f, -3f) + reflectiveQuadToRelative(13.5f, -1f) + quadToRelative(7f, 0f, 13.5f, 1f) + reflectiveQuadToRelative(12.5f, 3f) + lineToRelative(214f, 75f) + lineToRelative(186f, -72f) + quadToRelative(10f, -4f, 19.5f, -2.5f) + reflectiveQuadTo(823f, 136f) + quadToRelative(8f, 5f, 12.5f, 13.5f) + reflectiveQuadTo(840f, 169f) + verticalLineToRelative(561f) + quadToRelative(0f, 13f, -7.5f, 23f) + reflectiveQuadTo(812f, 768f) + lineToRelative(-186f, 63f) + quadToRelative(-6f, 2f, -12.5f, 3f) + reflectiveQuadToRelative(-13.5f, 1f) + quadToRelative(-7f, 0f, -13.5f, -1f) + reflectiveQuadToRelative(-12.5f, -3f) + close() + moveTo(560f, 742f) + verticalLineToRelative(-468f) + lineToRelative(-160f, -56f) + verticalLineToRelative(468f) + lineToRelative(160f, 56f) + close() + moveTo(640f, 742f) + lineTo(760f, 702f) + verticalLineToRelative(-474f) + lineToRelative(-120f, 46f) + verticalLineToRelative(468f) + close() + moveTo(200f, 732f) + lineTo(320f, 686f) + verticalLineToRelative(-468f) + lineToRelative(-120f, 40f) + verticalLineToRelative(474f) + close() + moveTo(640f, 274f) + verticalLineToRelative(468f) + verticalLineToRelative(-468f) + close() + moveTo(320f, 218f) + verticalLineToRelative(468f) + verticalLineToRelative(-468f) + close() + } + } + .build() + + return map!! + } + +private var map: ImageVector? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/MeshtasticIcons.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/MeshtasticIcons.kt new file mode 100644 index 000000000..4afd06b93 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/MeshtasticIcons.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +object MeshtasticIcons diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Messages.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Messages.kt new file mode 100644 index 000000000..3d4dfd9d3 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Messages.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * This is from Material Symbols. + * + * @see + * [forum](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:forum:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=forum&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded) + */ +val MeshtasticIcons.Messages: ImageVector + get() { + if (messages != null) { + return messages!! + } + messages = + ImageVector.Builder( + name = "Outlined.Messages", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ) + .apply { + path(fill = SolidColor(Color.Black)) { + moveTo(840f, 824f) + quadToRelative(-8f, 0f, -15f, -3f) + reflectiveQuadToRelative(-13f, -9f) + lineToRelative(-92f, -92f) + lineTo(320f, 720f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(240f, 640f) + verticalLineToRelative(-40f) + horizontalLineToRelative(440f) + quadToRelative(33f, 0f, 56.5f, -23.5f) + reflectiveQuadTo(760f, 520f) + verticalLineToRelative(-280f) + horizontalLineToRelative(40f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(880f, 320f) + verticalLineToRelative(463f) + quadToRelative(0f, 18f, -12f, 29.5f) + reflectiveQuadTo(840f, 824f) + close() + moveTo(160f, 487f) + lineToRelative(47f, -47f) + horizontalLineToRelative(393f) + verticalLineToRelative(-280f) + lineTo(160f, 160f) + verticalLineToRelative(327f) + close() + moveTo(120f, 624f) + quadToRelative(-16f, 0f, -28f, -11.5f) + reflectiveQuadTo(80f, 583f) + verticalLineToRelative(-423f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(160f, 80f) + horizontalLineToRelative(440f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(680f, 160f) + verticalLineToRelative(280f) + quadToRelative(0f, 33f, -23.5f, 56.5f) + reflectiveQuadTo(600f, 520f) + lineTo(240f, 520f) + lineToRelative(-92f, 92f) + quadToRelative(-6f, 6f, -13f, 9f) + reflectiveQuadToRelative(-15f, 3f) + close() + moveTo(160f, 440f) + verticalLineToRelative(-280f) + verticalLineToRelative(280f) + close() + } + } + .build() + + return messages!! + } + +private var messages: ImageVector? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/NoDevice.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/NoDevice.kt new file mode 100644 index 000000000..d06e27724 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/NoDevice.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * This is from Material Symbols. + * + * @see + * [router_off](https://fonts.google.com/icons?icon.query=router+off&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded) + */ +val MeshtasticIcons.NoDevice: ImageVector + get() { + if (noDevice != null) { + return noDevice!! + } + noDevice = + ImageVector.Builder( + name = "Outlined.NoDevice", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ) + .apply { + path(fill = SolidColor(Color.Black)) { + moveTo(806f, 692f) + lineTo(600f, 486f) + verticalLineToRelative(-86f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(640f, 360f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(680f, 400f) + verticalLineToRelative(120f) + horizontalLineToRelative(80f) + quadToRelative(33f, 0f, 56.5f, 23.5f) + reflectiveQuadTo(840f, 600f) + verticalLineToRelative(78f) + quadToRelative(0f, 14f, -12f, 19f) + reflectiveQuadToRelative(-22f, -5f) + close() + moveTo(200f, 760f) + horizontalLineToRelative(446f) + lineTo(486f, 600f) + lineTo(200f, 600f) + verticalLineToRelative(160f) + close() + moveTo(200f, 840f) + quadToRelative(-33f, 0f, -56.5f, -23.5f) + reflectiveQuadTo(120f, 760f) + verticalLineToRelative(-160f) + quadToRelative(0f, -33f, 23.5f, -56.5f) + reflectiveQuadTo(200f, 520f) + horizontalLineToRelative(206f) + lineTo(83f, 197f) + quadToRelative(-12f, -12f, -12f, -28.5f) + reflectiveQuadTo(83f, 140f) + quadToRelative(12f, -12f, 28.5f, -12f) + reflectiveQuadToRelative(28.5f, 12f) + lineToRelative(680f, 680f) + quadToRelative(12f, 12f, 12f, 28f) + reflectiveQuadToRelative(-12f, 28f) + quadToRelative(-12f, 12f, -28.5f, 12f) + reflectiveQuadTo(763f, 876f) + lineToRelative(-37f, -36f) + lineTo(200f, 840f) + close() + moveTo(280f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(240f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(280f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(320f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(280f, 720f) + close() + moveTo(420f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(380f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(420f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(460f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(420f, 720f) + close() + moveTo(560f, 720f) + quadToRelative(-17f, 0f, -28.5f, -11.5f) + reflectiveQuadTo(520f, 680f) + quadToRelative(0f, -17f, 11.5f, -28.5f) + reflectiveQuadTo(560f, 640f) + quadToRelative(17f, 0f, 28.5f, 11.5f) + reflectiveQuadTo(600f, 680f) + quadToRelative(0f, 17f, -11.5f, 28.5f) + reflectiveQuadTo(560f, 720f) + close() + moveTo(200f, 760f) + verticalLineToRelative(-160f) + verticalLineToRelative(160f) + close() + moveTo(640f, 300f) + quadToRelative(-11f, 0f, -20f, 2f) + reflectiveQuadToRelative(-18f, 6f) + quadToRelative(-16f, 7f, -32.5f, 6f) + reflectiveQuadTo(541f, 301f) + quadToRelative(-12f, -12f, -11.5f, -29f) + reflectiveQuadToRelative(14.5f, -25f) + quadToRelative(21f, -13f, 45.5f, -20f) + reflectiveQuadToRelative(50.5f, -7f) + quadToRelative(27f, 0f, 51f, 7f) + reflectiveQuadToRelative(45f, 20f) + quadToRelative(14f, 8f, 14.5f, 25f) + reflectiveQuadTo(739f, 301f) + quadToRelative(-12f, 12f, -29f, 13f) + reflectiveQuadToRelative(-33f, -6f) + quadToRelative(-8f, -4f, -17.5f, -6f) + reflectiveQuadToRelative(-19.5f, -2f) + close() + moveTo(640f, 160f) + quadToRelative(-39f, 0f, -74.5f, 11.5f) + reflectiveQuadTo(500f, 205f) + quadToRelative(-14f, 10f, -30.5f, 9f) + reflectiveQuadTo(442f, 202f) + quadToRelative(-12f, -12f, -12f, -28f) + reflectiveQuadToRelative(13f, -26f) + quadToRelative(41f, -32f, 91f, -50f) + reflectiveQuadToRelative(106f, -18f) + quadToRelative(56f, 0f, 106f, 18f) + reflectiveQuadToRelative(91f, 50f) + quadToRelative(13f, 10f, 13f, 26f) + reflectiveQuadToRelative(-12f, 28f) + quadToRelative(-11f, 11f, -27.5f, 12f) + reflectiveQuadToRelative(-30.5f, -9f) + quadToRelative(-30f, -22f, -65.5f, -33.5f) + reflectiveQuadTo(640f, 160f) + close() + } + } + .build() + + return noDevice!! + } + +private var noDevice: ImageVector? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/icons/Nodes.kt b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Nodes.kt new file mode 100644 index 000000000..a388cb79c --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/common/icons/Nodes.kt @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.common.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +/** + * This is from Material Symbols. + * + * @see + * [graph_3](https://fonts.google.com/icons?icon.query=graph+3&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded) + */ +val MeshtasticIcons.Nodes: ImageVector + get() { + if (nodes != null) { + return nodes!! + } + nodes = + ImageVector.Builder( + name = "Outlined.Nodes", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ) + .apply { + path(fill = SolidColor(Color.Black)) { + moveTo(480f, 880f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -5f, 0.5f, -11f) + reflectiveQuadToRelative(1.5f, -11f) + lineToRelative(-83f, -47f) + quadToRelative(-16f, 14f, -36f, 21.5f) + reflectiveQuadToRelative(-43f, 7.5f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(24f, 0f, 45f, 9f) + reflectiveQuadToRelative(38f, 25f) + lineToRelative(119f, -60f) + quadToRelative(-3f, -23f, 2.5f, -45f) + reflectiveQuadToRelative(19.5f, -41f) + lineToRelative(-34f, -52f) + quadToRelative(-7f, 2f, -14.5f, 3f) + reflectiveQuadToRelative(-15.5f, 1f) + quadToRelative(-50f, 0f, -85f, -35f) + reflectiveQuadToRelative(-35f, -85f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 20f, -6.5f, 38.5f) + reflectiveQuadTo(456f, 272f) + lineToRelative(35f, 52f) + quadToRelative(8f, -2f, 15f, -3f) + reflectiveQuadToRelative(15f, -1f) + quadToRelative(17f, 0f, 32f, 4f) + reflectiveQuadToRelative(29f, 12f) + lineToRelative(66f, -54f) + quadToRelative(-4f, -10f, -6f, -20.5f) + reflectiveQuadToRelative(-2f, -21.5f) + quadToRelative(0f, -50f, 35f, -85f) + reflectiveQuadToRelative(85f, -35f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + quadToRelative(-17f, 0f, -32f, -4.5f) + reflectiveQuadTo(699f, 343f) + lineToRelative(-66f, 55f) + quadToRelative(4f, 10f, 6f, 20.5f) + reflectiveQuadToRelative(2f, 21.5f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + quadToRelative(-24f, 0f, -45.5f, -9f) + reflectiveQuadTo(437f, 526f) + lineToRelative(-118f, 59f) + quadToRelative(2f, 9f, 1.5f, 18f) + reflectiveQuadToRelative(-2.5f, 18f) + lineToRelative(84f, 48f) + quadToRelative(16f, -14f, 35.5f, -21.5f) + reflectiveQuadTo(480f, 640f) + quadToRelative(50f, 0f, 85f, 35f) + reflectiveQuadToRelative(35f, 85f) + quadToRelative(0f, 50f, -35f, 85f) + reflectiveQuadToRelative(-85f, 35f) + close() + moveTo(200f, 640f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(240f, 600f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(200f, 560f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(160f, 600f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(200f, 640f) + close() + moveTo(360f, 240f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(400f, 200f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(360f, 160f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(320f, 200f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(360f, 240f) + close() + moveTo(480f, 800f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(520f, 760f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(480f, 720f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(440f, 760f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(480f, 800f) + close() + moveTo(520f, 480f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(560f, 440f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(520f, 400f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(480f, 440f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(520f, 480f) + close() + moveTo(760f, 280f) + quadToRelative(17f, 0f, 28.5f, -11.5f) + reflectiveQuadTo(800f, 240f) + quadToRelative(0f, -17f, -11.5f, -28.5f) + reflectiveQuadTo(760f, 200f) + quadToRelative(-17f, 0f, -28.5f, 11.5f) + reflectiveQuadTo(720f, 240f) + quadToRelative(0f, 17f, 11.5f, 28.5f) + reflectiveQuadTo(760f, 280f) + close() + } + } + .build() + + return nodes!! + } + +private var nodes: ImageVector? = null diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt index 3ae785020..18f224b19 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt @@ -602,7 +602,7 @@ private tailrec fun Context.findActivity(): Activity = when (this) { else -> error("No activity found") } -private enum class DeviceType { +enum class DeviceType { BLE, TCP, USB, diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt new file mode 100644 index 000000000..8f6be8e90 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.connections.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Bluetooth +import androidx.compose.material.icons.rounded.Snooze +import androidx.compose.material.icons.rounded.Usb +import androidx.compose.material.icons.rounded.Wifi +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.geeksville.mesh.service.ConnectionState +import com.geeksville.mesh.ui.TopLevelDestination +import com.geeksville.mesh.ui.common.icons.Device +import com.geeksville.mesh.ui.common.icons.MeshtasticIcons +import com.geeksville.mesh.ui.common.icons.NoDevice +import com.geeksville.mesh.ui.common.theme.AppTheme +import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen +import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed +import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow +import com.geeksville.mesh.ui.connections.DeviceType + +@Composable +fun TopLevelNavIcon(destination: TopLevelDestination, connectionState: ConnectionState, deviceType: DeviceType?) { + if (destination == TopLevelDestination.Connections) { + ConnectionsNavIcon(connectionState = connectionState, deviceType = deviceType) + } else { + Icon( + imageVector = destination.icon, + contentDescription = stringResource(id = destination.label), + tint = LocalContentColor.current, + ) + } +} + +@Composable +private fun ConnectionsNavIcon( + modifier: Modifier = Modifier, + connectionState: ConnectionState, + deviceType: DeviceType?, +) { + val tint = + when (connectionState) { + ConnectionState.DISCONNECTED -> colorScheme.StatusRed + ConnectionState.DEVICE_SLEEP -> colorScheme.StatusYellow + else -> colorScheme.StatusGreen + } + + val (backgroundIcon, connectionTypeIcon) = + when (connectionState) { + ConnectionState.DISCONNECTED -> MeshtasticIcons.NoDevice to null + ConnectionState.DEVICE_SLEEP -> MeshtasticIcons.Device to Icons.Rounded.Snooze + else -> + MeshtasticIcons.Device to + when (deviceType) { + DeviceType.BLE -> Icons.Rounded.Bluetooth + DeviceType.TCP -> Icons.Rounded.Wifi + DeviceType.USB -> Icons.Rounded.Usb + else -> null + } + } + + val foregroundPainter = connectionTypeIcon?.let { rememberVectorPainter(it) } + + Icon( + imageVector = backgroundIcon, + contentDescription = null, + tint = tint, + modifier = + modifier.drawWithContent { + drawContent() + foregroundPainter?.let { + @Suppress("MagicNumber") + val badgeSize = size.width * .45f + with(it) { draw(Size(badgeSize, badgeSize), colorFilter = ColorFilter.tint(tint)) } + } + }, + ) +} + +class TopLevelDestinationProvider : PreviewParameterProvider { + override val values: Sequence = TopLevelDestination.entries.asSequence() +} + +class ConnectionStateProvider : PreviewParameterProvider { + override val values: Sequence = + sequenceOf(ConnectionState.CONNECTED, ConnectionState.DEVICE_SLEEP, ConnectionState.DISCONNECTED) +} + +class DeviceTypeProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(DeviceType.BLE, DeviceType.TCP, DeviceType.USB) +} + +@PreviewLightDark +@Composable +private fun TopLevelNavIconPreviewConnectionStates( + @PreviewParameter(TopLevelDestinationProvider::class) destination: TopLevelDestination, +) { + AppTheme { + TopLevelNavIcon( + destination = destination, + connectionState = ConnectionState.CONNECTED, + deviceType = DeviceType.BLE, + ) + } +} + +@PreviewLightDark +@Composable +private fun ConnectionsNavIconPreviewConnectionStates( + @PreviewParameter(ConnectionStateProvider::class) connectionState: ConnectionState, +) { + AppTheme { ConnectionsNavIcon(connectionState = connectionState, deviceType = DeviceType.BLE) } +} + +@Preview(showBackground = true) +@Composable +private fun ConnectionsNavIconPreviewDeviceTypes(@PreviewParameter(DeviceTypeProvider::class) deviceType: DeviceType) { + ConnectionsNavIcon(connectionState = ConnectionState.CONNECTED, deviceType = deviceType) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 828b01baf..4a01508ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -120,7 +120,7 @@ Connected: %1$s online IP Address: Port: - Connected to radio + Connected Connected to radio (%s) Not connected Connected to radio, but it is sleeping @@ -621,10 +621,11 @@ Load User String Navigate Into - Connections - Map - Contacts + Connection + Mesh Map + Messages Nodes + Share Set your region Reply Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, long and short name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name.