diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6376acf3b..ff4d77e56 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -183,6 +183,7 @@ dependencies { implementation(libs.bundles.androidx) implementation(libs.bundles.ui) debugImplementation(libs.bundles.ui.tooling) + implementation(libs.bundles.adaptive) implementation(libs.bundles.lifecycle) implementation(libs.bundles.navigation) implementation(libs.bundles.coroutines) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 019a44c18..235333149 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -37,15 +37,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.core.content.edit import androidx.core.net.toUri @@ -157,16 +152,10 @@ class MainActivity : AppCompatActivity(), Logging { AppCompatDelegate.setDefaultNightMode(theme) } } - Box( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .systemBarsPadding() - ) { - MainScreen( - viewModel = model, - onAction = ::onMainMenuAction - ) - } + MainScreen( + viewModel = model, + onAction = ::onMainMenuAction, + ) } } // Handle any intent 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 4dec8626e..4ad55cc17 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -19,7 +19,8 @@ package com.geeksville.mesh.ui import androidx.annotation.StringRes import androidx.compose.foundation.background -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -37,15 +38,15 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.PlainTooltip -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.Text import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TopAppBar +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -63,11 +64,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController 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.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController @@ -106,12 +105,12 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, } } -@Suppress("LongMethod") +@OptIn(ExperimentalMaterial3Api::class) +@Suppress("LongMethod", "CyclomaticComplexMethod") @Composable fun MainScreen( - modifier: Modifier = Modifier, viewModel: UIViewModel = hiltViewModel(), - onAction: (MainMenuAction) -> Unit + onAction: (MainMenuAction) -> Unit, ) { val navController = rememberNavController() val connectionState by viewModel.connectionState.collectAsStateWithLifecycle() @@ -158,36 +157,84 @@ fun MainScreen( onDismiss = { viewModel.clearTracerouteResponse() } ) } - - Scaffold( - modifier = modifier.safeDrawingPadding(), - topBar = { + val navSuiteType = + NavigationSuiteScaffoldDefaults.navigationSuiteType(currentWindowAdaptiveInfo()) + val currentDestination = navController.currentBackStackEntryAsState().value?.destination + val topLevelDestination = TopLevelDestination.fromNavDestination(currentDestination) + NavigationSuiteScaffold( + modifier = Modifier.safeDrawingPadding(), + navigationSuiteItems = { + TopLevelDestination.entries.forEach { destination -> + val isSelected = destination == topLevelDestination + val isConnectionsRoute = destination == TopLevelDestination.Connections + item( + icon = { + TooltipBox( + positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text( + if (isConnectionsRoute) { + connectionState.getTooltipString() + } else { + stringResource(id = destination.label) + }, + ) + } + }, + state = rememberTooltipState() + ) { + TopLevelNavIcon(destination, connectionState) + } + }, + selected = isSelected, + label = { + if (navSuiteType != NavigationSuiteType.ShortNavigationBarCompact) { + Text(stringResource(id = destination.label)) + } + }, + onClick = { + navController.navigate(destination.route) { + // Pop up to the start destination of the graph to + // avoid building up a large stack of destinations + // on the back stack as users select items +// destination.route +// popUpTo(navController.graph.findStartDestination().id) { +// saveState = true +// } + // Avoid multiple copies of the same destination when + // reselecting the same item + launchSingleTop = true + // Restore state when reselecting a previously selected item + restoreState = true + } + } + ) + } + } + ) { + Column( + modifier = Modifier + .fillMaxSize() + ) { MainAppBar( title = title, isManaged = localConfig.security.isManaged, navController = navController, - ) { action -> - when (action) { - MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel) - MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig()) - MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) - else -> onAction(action) - } - } - }, - bottomBar = { - BottomNavigation( - connectionState = connectionState, + onAction = { action -> + when (action) { + MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel) + MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig()) + MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) + else -> onAction(action) + } + }, + ) + NavGraph( + uIViewModel = viewModel, navController = navController, ) - }, - snackbarHost = { SnackbarHost(hostState = viewModel.snackbarState) } - ) { innerPadding -> - NavGraph( - modifier = Modifier.padding(innerPadding), - uIViewModel = viewModel, - navController = navController, - ) + } } } @@ -378,62 +425,6 @@ private fun MainMenuActions( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun BottomNavigation( - connectionState: MeshService.ConnectionState, - navController: NavController, -) { - val currentDestination = navController.currentBackStackEntryAsState().value?.destination - val topLevelDestination = TopLevelDestination.fromNavDestination(currentDestination) - - NavigationBar { - TopLevelDestination.entries.forEach { destination -> - val isSelected = destination == topLevelDestination - val isConnectionsRoute = destination == TopLevelDestination.Connections - NavigationBarItem( - icon = { - TooltipBox( - positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), - tooltip = { - PlainTooltip { - Text( - if (isConnectionsRoute) { - connectionState.getTooltipString() - } else { - stringResource(id = destination.label) - }, - ) - } - }, - state = rememberTooltipState() - ) { - TopLevelNavIcon(destination, connectionState) - } - }, - selected = isSelected, - onClick = { - if (!isSelected) { - navController.navigate(destination.route) { - // Pop up to the start destination of the graph to - // avoid building up a large stack of destinations - // on the back stack as users select items - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - // Avoid multiple copies of the same destination when - // reselecting the same item - launchSingleTop = true - // Restore state when reselecting a previously selected item - restoreState = true - } - } - } - ) - } - } -} - @Composable private fun MeshService.ConnectionState.getConnectionColor(): Color { return when (this) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14f94a1bd..e04b702ec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,6 @@ [versions] +adaptive = "1.2.0-alpha06" +adaptive-navigation-suite = "1.3.2" agp = "8.10.1" appcompat = "1.7.1" appintro = "6.3.1" @@ -53,6 +55,11 @@ agp = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" activity = { group = "androidx.activity", name = "activity" } actvity-ktx = { group = "androidx.activity", name = "activity-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose" } +adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" } +adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "adaptive" } +adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "adaptive" } +adaptive-navigation-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation-android", version.ref = "adaptive" } +adaptive-navigation-suite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "adaptive-navigation-suite" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } appcompat-resources = { group = "androidx.appcompat", name = "appcompat-resources", version.ref = "appcompat" } appintro = { group = "com.github.AppIntro", name = "AppIntro", version.ref = "appintro" } @@ -139,6 +146,7 @@ androidx = ["core-ktx", "appcompat", "appcompat-resources", "cardview", "fragmen # UI ui = ["material", "constraintlayout", "viewpager2", "compose-material3", "compose-material-icons-extended", "compose-ui-tooling-preview", "compose-runtime-livedata"] +adaptive = ["adaptive", "adaptive-layout", "adaptive-navigation", "adaptive-navigation-android", "adaptive-navigation-suite"] ui-tooling = ["compose-ui-tooling"] #Separate for debugImplementation # Lifecycle