@@ -50,7 +50,6 @@ import androidx.compose.runtime.LaunchedEffect
5050import androidx.compose.runtime.MutableState
5151import androidx.compose.runtime.collectAsState
5252import androidx.compose.runtime.getValue
53- import androidx.compose.runtime.mutableIntStateOf
5453import androidx.compose.runtime.mutableStateOf
5554import androidx.compose.runtime.remember
5655import androidx.compose.runtime.setValue
@@ -91,6 +90,7 @@ import network.columba.app.navigation.navigateToAnsweredCall
9190import network.columba.app.navigation.navigateToEntity
9291import network.columba.app.navigation.navigateToIncomingCall
9392import network.columba.app.navigation.shouldPresentIncomingCall
93+ import network.columba.app.navigation.NavTab
9494import network.columba.app.notifications.CallNotificationHelper
9595import network.columba.app.repository.InterfaceRepository
9696import network.columba.app.repository.SettingsRepository
@@ -741,7 +741,6 @@ fun ColumbaNavigation(
741741 val context = LocalContext .current
742742 val lifecycleOwner = LocalLifecycleOwner .current
743743 val navController = rememberNavController()
744- var selectedTab by remember { mutableIntStateOf(0 ) }
745744
746745 LaunchedEffect (detachedUsbDeviceEvents, navController) {
747746 detachedUsbDeviceEvents.collect { deviceId ->
@@ -926,7 +925,6 @@ fun ColumbaNavigation(
926925 }
927926 is PendingNavigation .AddContact -> {
928927 // Navigate to contacts tab and trigger add contact dialog
929- selectedTab = 1 // Contacts tab
930928 navController.navigate(Screen .Contacts .route) {
931929 popUpTo(navController.graph.startDestinationId) {
932930 saveState = true
@@ -950,7 +948,6 @@ fun ColumbaNavigation(
950948 is PendingNavigation .SharedText -> {
951949 sharedTextViewModel.setText(navigation.text)
952950
953- selectedTab = 0
954951 val poppedToChats = navController.popBackStack(Screen .Chats .route, inclusive = false )
955952 if (! poppedToChats) {
956953 navController.navigate(Screen .Chats .route) {
@@ -966,7 +963,6 @@ fun ColumbaNavigation(
966963 is PendingNavigation .SharedImage -> {
967964 sharedImageViewModel.setImages(navigation.uris)
968965
969- selectedTab = 0
970966 val poppedToChats = navController.popBackStack(Screen .Chats .route, inclusive = false )
971967 if (! poppedToChats) {
972968 navController.navigate(Screen .Chats .route) {
@@ -1170,19 +1166,6 @@ fun ColumbaNavigation(
11701166 val navBackStackEntry by navController.currentBackStackEntryAsState()
11711167 val currentRoute = navBackStackEntry?.destination?.route
11721168
1173- // Synchronize selectedTab with current route when navigating back
1174- LaunchedEffect (currentRoute) {
1175- Log .d(" ColumbaNavigation" , " 📍 currentRoute changed to: $currentRoute " )
1176- selectedTab =
1177- when (currentRoute) {
1178- Screen .Chats .route -> 0
1179- Screen .Contacts .route -> 1
1180- Screen .Map .route -> 2
1181- Screen .Settings .route -> 3
1182- else -> selectedTab // Keep current selection for nested screens
1183- }
1184- }
1185-
11861169 // Observe call state for incoming calls and navigate to IncomingCallScreen.
11871170 // Composable functions can't @Inject, so the RnsTelephony seam singleton is
11881171 // reached through Hilt's RnsTelephonyEntryPoint. Replaces the A.9-era
@@ -1266,7 +1249,9 @@ fun ColumbaNavigation(
12661249 listOf (
12671250 " offline_map_download" ,
12681251 " messaging/" ,
1269- " announce_detail/" ,
1252+ // announce_detail (Node Details) intentionally keeps the nav bar:
1253+ // it sits one tap from the tabs, and hiding the bar made returning
1254+ // from NomadNet flows feel jarring.
12701255 " message_detail/" ,
12711256 " theme_editor" ,
12721257 " rnode_wizard" ,
@@ -1277,20 +1262,15 @@ fun ColumbaNavigation(
12771262 " voice_call/" ,
12781263 " incoming_call/" ,
12791264 " interface_stats/" ,
1280- " nomadnet_browser/" ,
12811265 )
12821266 val shouldShowBottomNav =
12831267 currentRoute != null &&
12841268 currentRoute !in hideBottomNavScreens &&
12851269 hideBottomNavPrefixes.none { currentRoute.startsWith(it) }
12861270
1287- val screens =
1288- listOf (
1289- Screen .Chats ,
1290- Screen .Contacts ,
1291- Screen .Map ,
1292- Screen .Settings ,
1293- )
1271+ // User-configurable bottom bar tabs (Settings pinned last, max NavTab.MAX_TABS).
1272+ // The NomadNet tab is a normal tab: the bar stays visible while browsing pages.
1273+ val bottomNavTabs = settingsState.bottomNavTabs
12941274
12951275 // Double-back-to-exit state: first back press on a root tab shows a toast,
12961276 // second press within 2 seconds finishes the activity.
@@ -1345,14 +1325,32 @@ fun ColumbaNavigation(
13451325 bottomBar = {
13461326 if (shouldShowBottomNav) {
13471327 NavigationBar {
1348- screens.forEachIndexed { index, screen ->
1328+ bottomNavTabs.forEach { tab ->
13491329 NavigationBarItem (
1350- icon = { Icon (screen .icon, contentDescription = null ) },
1351- label = { Text (screen.title ) },
1352- selected = selectedTab == index ,
1330+ icon = { Icon (tab .icon, contentDescription = null ) },
1331+ label = { Text (tab.label ) },
1332+ selected = tab.matchesRoute(currentRoute) ,
13531333 onClick = {
1354- selectedTab = index
1355- navController.navigate(screen.route) {
1334+ if (currentRoute?.startsWith(" nomadnet" ) == true ) {
1335+ if (tab == NavTab .NOMADNET ) {
1336+ // Already browsing; the site session ends via
1337+ // Close Site or Back, not by re-tapping the tab.
1338+ return @NavigationBarItem
1339+ }
1340+ // Browsing is modal over the tab tree: collapse
1341+ // the NomadNet stack first, then switch tabs
1342+ // normally. Popping (rather than saving state)
1343+ // guarantees a single browser view, so tab taps
1344+ // can never stack duplicates or fight over
1345+ // scroll position.
1346+ while (
1347+ navController.currentDestination?.route
1348+ ?.startsWith(" nomadnet" ) == true
1349+ ) {
1350+ if (! navController.popBackStack()) break
1351+ }
1352+ }
1353+ navController.navigate(tab.tabRoute) {
13561354 popUpTo(navController.graph.startDestinationId) {
13571355 saveState = true
13581356 }
@@ -1741,7 +1739,6 @@ fun ColumbaNavigation(
17411739 navController.navigate(" apk_sharing" )
17421740 },
17431741 onNavigateToAnnounces = { filterType ->
1744- selectedTab = 1 // Announces tab
17451742 val route =
17461743 if (filterType != null ) {
17471744 " ${Screen .Announces .route} ?filterType=$filterType "
@@ -2422,7 +2419,6 @@ fun ColumbaNavigation(
24222419 },
24232420 onStartChat = { destHash, peerName ->
24242421 // Navigate back to chats tab
2425- selectedTab = 0
24262422 navController.navigate(Screen .Chats .route) {
24272423 popUpTo(navController.graph.startDestinationId) {
24282424 saveState = true
@@ -2467,6 +2463,31 @@ fun ColumbaNavigation(
24672463 destinationHash = destHash,
24682464 initialPath = path,
24692465 onBackClick = { navController.popBackStack() },
2466+ // Standalone browser: after closing the site,
2467+ // pop back the way Back does.
2468+ onCloseSite = { navController.popBackStack() },
2469+ onOpenConversation = { conversationHash ->
2470+ val encodedHash = Uri .encode(conversationHash)
2471+ val encodedName = Uri .encode(conversationHash.take(12 ))
2472+ navController.navigate(" messaging/$encodedHash /$encodedName " )
2473+ },
2474+ )
2475+ }
2476+
2477+ // NomadNet tab home: reopens the last-browsed node,
2478+ // or shows the address-entry prompt on a fresh install.
2479+ appComposable(AppDestination .NOMADNET_HOME ) {
2480+ DoubleBackToExitHandler (AppDestination .NOMADNET_HOME .routePattern)
2481+ val lastNodeHash = settingsState.nomadNetLastNodeHash
2482+ NomadNetBrowserScreen (
2483+ destinationHash = lastNodeHash.orEmpty(),
2484+ showHomeEntry = lastNodeHash.isNullOrEmpty(),
2485+ onBackClick = { navController.popBackStack() },
2486+ // Close Site on the tab home: closeSite() drops the
2487+ // persisted last-node hash, the state flow flips
2488+ // showHomeEntry, and the screen swaps to the address
2489+ // prompt in place - no navigation needed.
2490+ onCloseSite = {},
24702491 onOpenConversation = { conversationHash ->
24712492 val encodedHash = Uri .encode(conversationHash)
24722493 val encodedName = Uri .encode(conversationHash.take(12 ))
0 commit comments