diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 15128bdcd..2bf446b9d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,7 +31,7 @@ plugins { alias(libs.plugins.meshtastic.hilt) alias(libs.plugins.meshtastic.android.room) alias(libs.plugins.kotlin.parcelize) - alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.meshtastic.kotlinx.serialization) alias(libs.plugins.protobuf) alias(libs.plugins.devtools.ksp) alias(libs.plugins.datadog) @@ -200,7 +200,9 @@ androidComponents { project.afterEvaluate { logger.lifecycle("Version code is set to: ${android.defaultConfig.versionCode}") } dependencies { - implementation(project(":network")) + implementation(projects.navigation) + implementation(projects.network) + // Bundles implementation(libs.bundles.markdown) implementation(libs.bundles.coroutines) diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt similarity index 93% rename from app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt index cec77872e..18a540607 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt @@ -28,14 +28,6 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen import com.geeksville.mesh.ui.sharing.ChannelScreen -import kotlinx.serialization.Serializable - -@Serializable -sealed class ChannelsRoutes { - @Serializable data object ChannelsGraph : Graph - - @Serializable data object Channels : Route -} /** Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels]. */ fun NavGraphBuilder.channelsGraph(navController: NavHostController, uiViewModel: UIViewModel) { diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt similarity index 93% rename from app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt index c0dadee36..be5e81c32 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt @@ -28,14 +28,6 @@ import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.connections.ConnectionsScreen import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen -import kotlinx.serialization.Serializable - -@Serializable -sealed class ConnectionsRoutes { - @Serializable data object ConnectionsGraph : Graph - - @Serializable data object Connections : Route -} /** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */ fun NavGraphBuilder.connectionsGraph( diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ContactsNavigation.kt similarity index 90% rename from app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/ContactsNavigation.kt index 980d84c4d..db090d996 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ContactsNavigation.kt @@ -28,19 +28,6 @@ import com.geeksville.mesh.ui.contact.ContactsScreen import com.geeksville.mesh.ui.message.MessageScreen import com.geeksville.mesh.ui.message.QuickChatScreen import com.geeksville.mesh.ui.sharing.ShareScreen -import kotlinx.serialization.Serializable - -sealed class ContactsRoutes { - @Serializable data object Contacts : Route - - @Serializable data class Messages(val contactKey: String, val message: String = "") : Route - - @Serializable data class Share(val message: String) : Route - - @Serializable data object QuickChat : Route - - @Serializable data object ContactsGraph : Graph -} @Suppress("LongMethod") fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel: UIViewModel) { diff --git a/app/src/main/java/com/geeksville/mesh/navigation/MapRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/MapNavigation.kt similarity index 84% rename from app/src/main/java/com/geeksville/mesh/navigation/MapRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/MapNavigation.kt index 9c0947264..c9b75373f 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/MapRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/MapNavigation.kt @@ -23,18 +23,11 @@ import androidx.navigation.compose.composable import androidx.navigation.navDeepLink import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.map.MapView -import com.geeksville.mesh.ui.map.MapViewModel -import kotlinx.serialization.Serializable -sealed class MapRoutes { - @Serializable data object Map : Route -} - -fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel, mapViewModel: MapViewModel) { +fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel) { composable(deepLinks = listOf(navDeepLink(basePath = "$DEEP_LINK_BASE_URI/map"))) { MapView( uiViewModel = uiViewModel, - mapViewModel = mapViewModel, navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) }, ) } diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt deleted file mode 100644 index 7393b9d38..000000000 --- a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.navigation - -import androidx.annotation.StringRes -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.PowerSettingsNew -import androidx.compose.material.icons.rounded.RestartAlt -import androidx.compose.material.icons.rounded.Restore -import androidx.compose.material.icons.rounded.Storage -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.NavDestination -import androidx.navigation.NavDestination.Companion.hasRoute -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import com.geeksville.mesh.R -import com.geeksville.mesh.model.BluetoothViewModel -import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel -import com.geeksville.mesh.ui.map.MapViewModel -import kotlinx.serialization.Serializable - -enum class AdminRoute(val icon: ImageVector, @StringRes val title: Int) { - REBOOT(Icons.Rounded.RestartAlt, R.string.reboot), - SHUTDOWN(Icons.Rounded.PowerSettingsNew, R.string.shutdown), - FACTORY_RESET(Icons.Rounded.Restore, R.string.factory_reset), - NODEDB_RESET(Icons.Rounded.Storage, R.string.nodedb_reset), -} - -const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic" - -@Serializable sealed interface Graph : Route - -@Serializable sealed interface Route - -fun NavDestination.isConfigRoute(): Boolean = - ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) } - -fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any { hasRoute(it.route::class) } - -fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() && - ( - this.hasRoute() || - this.hasRoute() || - this.isConfigRoute() || - this.isNodeDetailRoute() - ) - -@Suppress("LongMethod") -@Composable -fun NavGraph( - modifier: Modifier = Modifier, - uIViewModel: UIViewModel = hiltViewModel(), - bluetoothViewModel: BluetoothViewModel = hiltViewModel(), - mapViewModel: MapViewModel = hiltViewModel(), - navController: NavHostController = rememberNavController(), -) { - NavHost(navController = navController, startDestination = NodesRoutes.NodesGraph, modifier = modifier) { - contactsGraph(navController, uIViewModel) - nodesGraph(navController, uIViewModel) - mapGraph(navController, uIViewModel, mapViewModel) - channelsGraph(navController, uIViewModel) - connectionsGraph(navController, uIViewModel, bluetoothViewModel) - settingsGraph(navController, uIViewModel) - } -} diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt similarity index 92% rename from app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt index 3f96b52c4..7f87dfebd 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/NodesNavigation.kt @@ -32,6 +32,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.ImageVector import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable @@ -51,37 +53,6 @@ import com.geeksville.mesh.ui.metrics.TracerouteLogScreen import com.geeksville.mesh.ui.node.NodeDetailScreen import com.geeksville.mesh.ui.node.NodeMapScreen import com.geeksville.mesh.ui.node.NodeScreen -import kotlinx.serialization.Serializable - -sealed class NodesRoutes { - @Serializable data object Nodes : Route - - @Serializable data object NodesGraph : Graph - - @Serializable data class NodeDetailGraph(val destNum: Int? = null) : Graph - - @Serializable data class NodeDetail(val destNum: Int? = null) : Route -} - -sealed class NodeDetailRoutes { - @Serializable data object DeviceMetrics : Route - - @Serializable data object NodeMap : Route - - @Serializable data object PositionLog : Route - - @Serializable data object EnvironmentMetrics : Route - - @Serializable data object SignalMetrics : Route - - @Serializable data object PowerMetrics : Route - - @Serializable data object TracerouteLog : Route - - @Serializable data object HostMetricsLog : Route - - @Serializable data object PaxMetrics : Route -} fun NavGraphBuilder.nodesGraph(navController: NavHostController, uiViewModel: UIViewModel) { navigation(startDestination = NodesRoutes.Nodes) { @@ -191,6 +162,8 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, uiViewMode } } +fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any { hasRoute(it.route::class) } + /** * Helper to define a composable route for a screen within the node detail graph. * diff --git a/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt similarity index 92% rename from app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt rename to app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt index 6026de27f..2d67f645b 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/SettingsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt @@ -47,6 +47,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.ImageVector import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable @@ -83,75 +85,6 @@ import com.geeksville.mesh.ui.settings.radio.components.SerialConfigScreen import com.geeksville.mesh.ui.settings.radio.components.StoreForwardConfigScreen import com.geeksville.mesh.ui.settings.radio.components.TelemetryConfigScreen import com.geeksville.mesh.ui.settings.radio.components.UserConfigScreen -import kotlinx.serialization.Serializable - -sealed class SettingsRoutes { - @Serializable data class SettingsGraph(val destNum: Int? = null) : Graph - - @Serializable data class Settings(val destNum: Int? = null) : Route - - // region radio Config Routes - - @Serializable data object User : Route - - @Serializable data object ChannelConfig : Route - - @Serializable data object Device : Route - - @Serializable data object Position : Route - - @Serializable data object Power : Route - - @Serializable data object Network : Route - - @Serializable data object Display : Route - - @Serializable data object LoRa : Route - - @Serializable data object Bluetooth : Route - - @Serializable data object Security : Route - - // endregion - - // region module config routes - - @Serializable data object MQTT : Route - - @Serializable data object Serial : Route - - @Serializable data object ExtNotification : Route - - @Serializable data object StoreForward : Route - - @Serializable data object RangeTest : Route - - @Serializable data object Telemetry : Route - - @Serializable data object CannedMessage : Route - - @Serializable data object Audio : Route - - @Serializable data object RemoteHardware : Route - - @Serializable data object NeighborInfo : Route - - @Serializable data object AmbientLighting : Route - - @Serializable data object DetectionSensor : Route - - @Serializable data object Paxcounter : Route - - // endregion - - // region advanced config routes - - @Serializable data object CleanNodeDb : Route - - @Serializable data object DebugPanel : Route - - // endregion -} fun getNavRouteFrom(routeName: String): Route? = ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route @@ -189,6 +122,9 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel: } } +fun NavDestination.isConfigRoute(): Boolean = + ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) } + /** * Helper to define a composable route for a radio configuration screen within the radio config graph. * 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 d096a5e44..c2975f923 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -70,6 +70,7 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.geeksville.mesh.BuildConfig @@ -86,10 +87,15 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.ConnectionsRoutes import com.geeksville.mesh.navigation.ContactsRoutes import com.geeksville.mesh.navigation.MapRoutes -import com.geeksville.mesh.navigation.NavGraph import com.geeksville.mesh.navigation.NodesRoutes import com.geeksville.mesh.navigation.Route import com.geeksville.mesh.navigation.SettingsRoutes +import com.geeksville.mesh.navigation.channelsGraph +import com.geeksville.mesh.navigation.connectionsGraph +import com.geeksville.mesh.navigation.contactsGraph +import com.geeksville.mesh.navigation.mapGraph +import com.geeksville.mesh.navigation.nodesGraph +import com.geeksville.mesh.navigation.settingsGraph import com.geeksville.mesh.repository.radio.MeshActivity import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.service.MeshService @@ -363,12 +369,19 @@ fun MainScreen( } }, ) - NavGraph( - modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(), - uIViewModel = uIViewModel, - bluetoothViewModel = bluetoothViewModel, + + NavHost( navController = navController, - ) + startDestination = NodesRoutes.NodesGraph, + modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(), + ) { + contactsGraph(navController, uiViewModel = uIViewModel) + nodesGraph(navController, uiViewModel = uIViewModel) + mapGraph(navController, uiViewModel = uIViewModel) + channelsGraph(navController, uiViewModel = uIViewModel) + connectionsGraph(navController, uiViewModel = uIViewModel, bluetoothViewModel) + settingsGraph(navController, uiViewModel = uIViewModel) + } } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt index 9a4c0316b..8a2d9454e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/common/components/MainAppBar.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState @@ -51,7 +52,8 @@ import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.ContactsRoutes import com.geeksville.mesh.navigation.NodesRoutes import com.geeksville.mesh.navigation.SettingsRoutes -import com.geeksville.mesh.navigation.showLongNameTitle +import com.geeksville.mesh.navigation.isConfigRoute +import com.geeksville.mesh.navigation.isNodeDetailRoute import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.debug.DebugMenuActions @@ -178,6 +180,14 @@ private fun MainAppBar( ) } +fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() && + ( + this.hasRoute() || + this.hasRoute() || + this.isConfigRoute() || + this.isNodeDetailRoute() + ) + @Composable private fun TopBarActions( ourNode: Node?, diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt index 2cf523b5d..431217951 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfig.kt @@ -17,6 +17,7 @@ package com.geeksville.mesh.ui.settings.radio +import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons @@ -24,6 +25,10 @@ import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Upload import androidx.compose.material.icons.rounded.BugReport import androidx.compose.material.icons.rounded.CleaningServices +import androidx.compose.material.icons.rounded.PowerSettingsNew +import androidx.compose.material.icons.rounded.RestartAlt +import androidx.compose.material.icons.rounded.Restore +import androidx.compose.material.icons.rounded.Storage import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -33,11 +38,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.geeksville.mesh.R -import com.geeksville.mesh.navigation.AdminRoute import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.ModuleRoute import com.geeksville.mesh.navigation.Route @@ -160,6 +165,13 @@ fun RadioConfigItemList( } } +enum class AdminRoute(val icon: ImageVector, @StringRes val title: Int) { + REBOOT(Icons.Rounded.RestartAlt, R.string.reboot), + SHUTDOWN(Icons.Rounded.PowerSettingsNew, R.string.shutdown), + FACTORY_RESET(Icons.Rounded.Restore, R.string.factory_reset), + NODEDB_RESET(Icons.Rounded.Storage, R.string.nodedb_reset), +} + @Preview(showBackground = true) @Composable private fun RadioSettingsScreenPreview() = AppTheme { diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt index e9ac7234a..ad38ad28e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt @@ -56,7 +56,6 @@ import com.geeksville.mesh.model.getChannelList import com.geeksville.mesh.model.getStringResFrom import com.geeksville.mesh.model.toChannelSet import com.geeksville.mesh.moduleConfig -import com.geeksville.mesh.navigation.AdminRoute import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.ModuleRoute import com.geeksville.mesh.navigation.SettingsRoutes diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 81754164d..20fa59879 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -90,6 +90,10 @@ gradlePlugin { id = libs.plugins.meshtastic.android.application.compose.get().pluginId implementationClass = "AndroidApplicationComposeConventionPlugin" } + register("kotlinXSerialization") { + id = libs.plugins.meshtastic.kotlinx.serialization.get().pluginId + implementationClass = "KotlinXSerializationConventionPlugin" + } register("meshtasticHilt") { id = libs.plugins.meshtastic.hilt.get().pluginId implementationClass = "HiltConventionPlugin" diff --git a/build-logic/convention/src/main/kotlin/KotlinXSerializationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/KotlinXSerializationConventionPlugin.kt new file mode 100644 index 000000000..890b48f1f --- /dev/null +++ b/build-logic/convention/src/main/kotlin/KotlinXSerializationConventionPlugin.kt @@ -0,0 +1,34 @@ +/* + * 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 . + */ + +import com.geeksville.mesh.buildlogic.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.dependencies + +class KotlinXSerializationConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + apply(plugin = "org.jetbrains.kotlin.plugin.serialization") + + dependencies { + "implementation"(libs.findLibrary("kotlinx-serialization-core").get()) + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6c5dd7be..bb5a52a52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ room = "2.8.0" # Kotlin kotlin = "2.2.20" kotlinx-coroutines-android = "1.10.2" +kotlinx-serialization = "1.9.0" # Google hilt = "2.57.1" @@ -112,7 +113,8 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" } kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines-android" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.9.0" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } # Networking ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } @@ -258,4 +260,5 @@ meshtastic-android-room = { id = "meshtastic.android.room" } meshtastic-android-test = { id = "meshtastic.android.test" } meshtastic-detekt = { id = "meshtastic.detekt" } meshtastic-hilt = { id = "meshtastic.hilt" } +meshtastic-kotlinx-serialization = { id = "meshtastic.kotlinx.serialization" } meshtastic-spotless = { id = "meshtastic.spotless" } diff --git a/navigation/.gitignore b/navigation/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/navigation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts new file mode 100644 index 000000000..5df3d5509 --- /dev/null +++ b/navigation/build.gradle.kts @@ -0,0 +1,25 @@ +/* + * 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 . + */ + +plugins { + alias(libs.plugins.meshtastic.android.library) + alias(libs.plugins.meshtastic.kotlinx.serialization) +} + +android { namespace = "com.geeksville.mesh.navigation" } + +dependencies {} diff --git a/navigation/src/main/kotlin/com/geeksville/mesh/navigation/Routes.kt b/navigation/src/main/kotlin/com/geeksville/mesh/navigation/Routes.kt new file mode 100644 index 000000000..519d83fde --- /dev/null +++ b/navigation/src/main/kotlin/com/geeksville/mesh/navigation/Routes.kt @@ -0,0 +1,152 @@ +/* + * 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.navigation + +import kotlinx.serialization.Serializable + +const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic" + +interface Route + +interface Graph : Route + +object ChannelsRoutes { + @Serializable data object ChannelsGraph : Graph + + @Serializable data object Channels : Route +} + +object ConnectionsRoutes { + @Serializable data object ConnectionsGraph : Graph + + @Serializable data object Connections : Route +} + +object ContactsRoutes { + @Serializable data object ContactsGraph : Graph + + @Serializable data object Contacts : Route + + @Serializable data class Messages(val contactKey: String, val message: String = "") : Route + + @Serializable data class Share(val message: String) : Route + + @Serializable data object QuickChat : Route +} + +object MapRoutes { + @Serializable data object Map : Route +} + +object NodesRoutes { + @Serializable data object NodesGraph : Graph + + @Serializable data object Nodes : Route + + @Serializable data class NodeDetailGraph(val destNum: Int? = null) : Graph + + @Serializable data class NodeDetail(val destNum: Int? = null) : Route +} + +object NodeDetailRoutes { + @Serializable data object DeviceMetrics : Route + + @Serializable data object NodeMap : Route + + @Serializable data object PositionLog : Route + + @Serializable data object EnvironmentMetrics : Route + + @Serializable data object SignalMetrics : Route + + @Serializable data object PowerMetrics : Route + + @Serializable data object TracerouteLog : Route + + @Serializable data object HostMetricsLog : Route + + @Serializable data object PaxMetrics : Route +} + +object SettingsRoutes { + @Serializable data class SettingsGraph(val destNum: Int? = null) : Graph + + @Serializable data class Settings(val destNum: Int? = null) : Route + + // region radio Config Routes + + @Serializable data object User : Route + + @Serializable data object ChannelConfig : Route + + @Serializable data object Device : Route + + @Serializable data object Position : Route + + @Serializable data object Power : Route + + @Serializable data object Network : Route + + @Serializable data object Display : Route + + @Serializable data object LoRa : Route + + @Serializable data object Bluetooth : Route + + @Serializable data object Security : Route + + // endregion + + // region module config routes + + @Serializable data object MQTT : Route + + @Serializable data object Serial : Route + + @Serializable data object ExtNotification : Route + + @Serializable data object StoreForward : Route + + @Serializable data object RangeTest : Route + + @Serializable data object Telemetry : Route + + @Serializable data object CannedMessage : Route + + @Serializable data object Audio : Route + + @Serializable data object RemoteHardware : Route + + @Serializable data object NeighborInfo : Route + + @Serializable data object AmbientLighting : Route + + @Serializable data object DetectionSensor : Route + + @Serializable data object Paxcounter : Route + + // endregion + + // region advanced config routes + + @Serializable data object CleanNodeDb : Route + + @Serializable data object DebugPanel : Route + + // endregion +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7b427770d..203a17783 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,9 +17,12 @@ import org.gradle.kotlin.dsl.maven * along with this program. If not, see . */ -include(":app", ":network", ":mesh_service_example") +include(":app", ":mesh_service_example", ":navigation", ":network") rootProject.name = "MeshtasticAndroid" +// https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:type-safe-project-accessors +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + pluginManagement { includeBuild("build-logic") repositories {