Skip to content

Commit dde1f1d

Browse files
committed
navigation: enhance bottom navigation bar
1 parent 899a00a commit dde1f1d

1 file changed

Lines changed: 122 additions & 46 deletions

File tree

Lines changed: 122 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
package com.maazm7d.termuxhub.navigation
22

3-
import androidx.compose.foundation.layout.offset
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.fadeIn
5+
import androidx.compose.animation.fadeOut
6+
import androidx.compose.animation.slideInVertically
7+
import androidx.compose.animation.slideOutVertically
8+
import androidx.compose.animation.core.animateFloatAsState
9+
import androidx.compose.foundation.layout.Box
10+
import androidx.compose.foundation.layout.fillMaxSize
411
import androidx.compose.foundation.layout.padding
512
import androidx.compose.foundation.layout.size
13+
import androidx.compose.foundation.shape.RoundedCornerShape
614
import androidx.compose.material3.*
715
import androidx.compose.runtime.*
16+
import androidx.compose.ui.Alignment
817
import androidx.compose.ui.Modifier
918
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.graphics.graphicsLayer
20+
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
21+
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
22+
import androidx.compose.ui.input.nestedscroll.nestedScroll
1023
import androidx.compose.ui.unit.dp
1124
import androidx.navigation.NavDestination
1225
import androidx.navigation.NavDestination.Companion.hierarchy
@@ -22,14 +35,30 @@ fun TermuxHubAppNav(
2235
val backStackEntry by navController.currentBackStackEntryAsState()
2336
val currentDestination: NavDestination? = backStackEntry?.destination
2437

25-
var handledDeepLink by remember { mutableStateOf(false) }
38+
var deepLinkHandled by remember { mutableStateOf(false) }
39+
var isBottomBarVisible by remember { mutableStateOf(true) }
40+
41+
// Detect scroll direction
42+
val scrollBehavior = remember {
43+
object : NestedScrollConnection {
44+
override fun onPreScroll(
45+
available: androidx.compose.ui.geometry.Offset,
46+
source: NestedScrollSource
47+
): androidx.compose.ui.geometry.Offset {
48+
when {
49+
available.y < -5f -> isBottomBarVisible = false // scroll down
50+
available.y > 5f -> isBottomBarVisible = true // scroll up
51+
}
52+
return androidx.compose.ui.geometry.Offset.Zero
53+
}
54+
}
55+
}
2656

2757
LaunchedEffect(deepLinkToolId) {
2858
if (deepLinkToolId.isNullOrBlank()) return@LaunchedEffect
29-
if (handledDeepLink) return@LaunchedEffect
30-
31-
handledDeepLink = true
59+
if (deepLinkHandled) return@LaunchedEffect
3260

61+
deepLinkHandled = true
3362
navController.navigate("${Destinations.DETAILS}/$deepLinkToolId") {
3463
popUpTo(navController.graph.startDestinationId) {
3564
inclusive = false
@@ -45,51 +74,98 @@ fun TermuxHubAppNav(
4574
}
4675

4776
Scaffold(
48-
bottomBar = {
49-
if (showBottomBar) {
50-
NavigationBar(
51-
containerColor = MaterialTheme.colorScheme.surface
52-
) {
53-
bottomNavItems.forEach { item ->
54-
val isSelected = currentDestination
55-
?.hierarchy
56-
?.any { it.route == item.route } == true
77+
containerColor = Color.Transparent
78+
) { paddingValues ->
79+
Box(
80+
modifier = Modifier
81+
.fillMaxSize()
82+
.padding(paddingValues)
83+
.nestedScroll(scrollBehavior)
84+
) {
85+
AppNavHost(
86+
navController = navController,
87+
modifier = Modifier.fillMaxSize()
88+
)
5789

58-
NavigationBarItem(
59-
selected = isSelected,
60-
onClick = {
61-
navController.navigate(item.route) {
62-
popUpTo(Destinations.TOOLS) {
63-
saveState = true
64-
}
65-
launchSingleTop = true
66-
restoreState = true
90+
AnimatedVisibility(
91+
visible = showBottomBar && isBottomBarVisible,
92+
enter = slideInVertically { it / 2 } + fadeIn(),
93+
exit = slideOutVertically { it / 2 } + fadeOut(),
94+
modifier = Modifier
95+
.align(Alignment.BottomCenter)
96+
.padding(horizontal = 16.dp, vertical = 20.dp)
97+
) {
98+
BottomPillNavBar(
99+
currentDestination = currentDestination,
100+
onNavigate = { route ->
101+
navController.navigate(route) {
102+
popUpTo(Destinations.TOOLS) {
103+
saveState = true
104+
}
105+
launchSingleTop = true
106+
restoreState = true
107+
}
108+
}
109+
)
110+
}
111+
}
112+
}
113+
}
114+
115+
@Composable
116+
private fun BottomPillNavBar(
117+
currentDestination: NavDestination?,
118+
onNavigate: (String) -> Unit
119+
) {
120+
Surface(
121+
shape = RoundedCornerShape(32.dp),
122+
color = MaterialTheme.colorScheme.surface,
123+
tonalElevation = 6.dp,
124+
shadowElevation = 10.dp
125+
) {
126+
NavigationBar(
127+
containerColor = Color.Transparent,
128+
tonalElevation = 0.dp
129+
) {
130+
bottomNavItems.forEach { item ->
131+
val selected = currentDestination
132+
?.hierarchy
133+
?.any { it.route == item.route } == true
134+
135+
val scale by animateFloatAsState(
136+
targetValue = if (selected) 1.15f else 1f,
137+
label = "scale"
138+
)
139+
140+
val alpha by animateFloatAsState(
141+
targetValue = if (selected) 1f else 0.6f,
142+
label = "alpha"
143+
)
144+
145+
NavigationBarItem(
146+
selected = selected,
147+
onClick = { onNavigate(item.route) },
148+
icon = {
149+
Icon(
150+
imageVector = item.icon,
151+
contentDescription = null,
152+
modifier = Modifier
153+
.size(if (item.isHome) 26.dp else 22.dp)
154+
.graphicsLayer {
155+
scaleX = scale
156+
scaleY = scale
157+
this.alpha = alpha
67158
}
68-
},
69-
icon = {
70-
Icon(
71-
imageVector = item.icon,
72-
contentDescription = null,
73-
modifier = Modifier
74-
.offset(y = if (item.isHome) (-4).dp else 0.dp)
75-
.size(if (item.isHome) 30.dp else 22.dp)
76-
)
77-
},
78-
label = null,
79-
colors = NavigationBarItemDefaults.colors(
80-
indicatorColor = Color.Transparent,
81-
selectedIconColor = MaterialTheme.colorScheme.primary,
82-
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant
83-
)
84159
)
85-
}
86-
}
160+
},
161+
label = null,
162+
colors = NavigationBarItemDefaults.colors(
163+
indicatorColor = Color.Transparent,
164+
selectedIconColor = MaterialTheme.colorScheme.primary,
165+
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant
166+
)
167+
)
87168
}
88169
}
89-
) { innerPadding ->
90-
AppNavHost(
91-
navController = navController,
92-
modifier = Modifier.padding(innerPadding)
93-
)
94170
}
95171
}

0 commit comments

Comments
 (0)