fix(ux): keep Settings highlighted in bottom nav on drill-down sub-pages (issue #637) (#643)

* fix(ux): keep Settings highlighted in bottom nav on drill-down sub-pages (issue #637)

Map Settings drill-down destinations (SettingsConnection, SettingsAppearance, SettingsChat, SettingsNavBar, SettingsBehavior, SettingsAbout) back to SettingsScreen for bottom-nav selection only - navigation is unaffected.

- Add selectedBottomNavDestination() helper in Navigation.kt
- Extract bar-height logic into Modifier.bottomNavigationHeight() (ICON_AND_TEXT now uses heightIn(min=80.dp) so the standard bar can grow)
- BottomNavigationSelectionTest (JVM): selection mapping logic
- BottomNavigationLayoutTest (androidTest): per-mode bar height assertions

* docs(navigation): warn that new Settings sub-pages must be added to settingsDestinations

Addresses agy review (PR #643): documents the fragility of the hardcoded settingsDestinations set so future Settings drill-down screens don't silently drop the bottom-nav highlight. The sealed-class/marker-interface refactor was declined as scope creep (issue #637 explicitly scopes Settings-only; keys are flat data objects).

* fix: language selector

---------

Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-20 18:26:05 +03:00
1 parent 367d6e22f5
commit e18b25d065
3 files changed
+154 -8

No files matched your search

@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -68,6 +69,37 @@ import com.m57.hermescontrol.ui.authlogin.AuthLoginScreen as AuthLoginScreenCont
import com.m57.hermescontrol.ui.landing.LandingScreen as LandingScreenContent
import com.m57.hermescontrol.ui.pairing.PairingCodeEntryScreen as PairingCodeEntryScreenContent
// NOTE: if a new Settings drill-down screen is added, its NavKey MUST be added
// here too, or the bottom-nav Settings highlight will drop on that sub-page.
// (Keys are flat data objects, not a sealed hierarchy, so no type-based check
// is possible without a larger refactor — see issue #637.)
private val settingsDestinations: Set<NavKey> =
setOf(
SettingsScreen,
SettingsConnection,
SettingsAppearance,
SettingsChat,
SettingsNavBar,
SettingsBehavior,
SettingsAbout,
)
/**
* Keep Settings highlighted in the bottom nav while one of its drill-down
* sub-pages is visible. Navigation is unaffected — this only maps the
* *selection* key back to the Settings parent so the indicator doesn't drop.
*/
internal fun selectedBottomNavDestination(currentScreen: NavKey): NavKey =
if (currentScreen in settingsDestinations) SettingsScreen else currentScreen
/** Maps a compact bottom-nav display mode to its bar height. */
internal fun Modifier.bottomNavigationHeight(displayMode: BottomNavDisplayMode): Modifier =
when (displayMode) {
BottomNavDisplayMode.ICON_ONLY -> height(56.dp)
BottomNavDisplayMode.TEXT_ONLY -> height(44.dp)
BottomNavDisplayMode.ICON_AND_TEXT -> heightIn(min = 80.dp)
}
private fun resolveBottomNavItems(names: List<String>): List<ScreenDefinition> =
names.mapNotNull { name -> ScreenRegistry.ALL_SCREENS.firstOrNull { it.key::class.simpleName == name } }
@@ -173,6 +205,7 @@ fun MainNavigation(sessionId: String? = null) {
NavigationController.backStack = backStack
val currentScreen = backStack.lastOrNull() ?: startScreen
val selectedDestination = selectedBottomNavDestination(currentScreen)
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
@@ -298,14 +331,8 @@ fun MainNavigation(sessionId: String? = null) {
contentWindowInsets = WindowInsets.navigationBars,
bottomBar = {
if (showBottomBar) {
val barHeight =
when (bottomNavDisplayMode) {
BottomNavDisplayMode.ICON_ONLY -> 56.dp
BottomNavDisplayMode.TEXT_ONLY -> 44.dp
BottomNavDisplayMode.ICON_AND_TEXT -> 80.dp
}
NavigationBar(
modifier = Modifier.height(barHeight),
modifier = Modifier.bottomNavigationHeight(bottomNavDisplayMode),
) {
bottomNavItems.forEach { item ->
val showIcon =
@@ -315,7 +342,7 @@ fun MainNavigation(sessionId: String? = null) {
bottomNavDisplayMode == BottomNavDisplayMode.ICON_AND_TEXT ||
bottomNavDisplayMode == BottomNavDisplayMode.TEXT_ONLY
val isSelected = currentScreen == item.key
val isSelected = selectedDestination == item.key
NavigationBarItem(
selected = isSelected,