feat: adaptive nav (#2079)

pull/2086/head^2
James Rich 2025-06-13 02:34:01 +00:00 zatwierdzone przez GitHub
rodzic 606d1520d8
commit 95224c20ef
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 94 dodań i 105 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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