11package com.yapp.ndgl.ui
22
3+ import androidx.compose.animation.AnimatedVisibility
4+ import androidx.compose.animation.core.Spring
5+ import androidx.compose.animation.core.animateDpAsState
6+ import androidx.compose.animation.core.spring
7+ import androidx.compose.animation.expandHorizontally
8+ import androidx.compose.animation.fadeIn
9+ import androidx.compose.animation.fadeOut
10+ import androidx.compose.animation.shrinkHorizontally
11+ import androidx.compose.foundation.background
12+ import androidx.compose.foundation.clickable
13+ import androidx.compose.foundation.interaction.MutableInteractionSource
14+ import androidx.compose.foundation.layout.Arrangement
15+ import androidx.compose.foundation.layout.Box
16+ import androidx.compose.foundation.layout.BoxWithConstraints
17+ import androidx.compose.foundation.layout.Row
18+ import androidx.compose.foundation.layout.fillMaxHeight
19+ import androidx.compose.foundation.layout.fillMaxSize
20+ import androidx.compose.foundation.layout.fillMaxWidth
21+ import androidx.compose.foundation.layout.height
22+ import androidx.compose.foundation.layout.navigationBarsPadding
23+ import androidx.compose.foundation.layout.offset
24+ import androidx.compose.foundation.layout.padding
25+ import androidx.compose.foundation.layout.size
26+ import androidx.compose.foundation.layout.width
27+ import androidx.compose.foundation.shape.CircleShape
28+ import androidx.compose.foundation.shape.RoundedCornerShape
329import androidx.compose.material3.Icon
4- import androidx.compose.material3.NavigationBar
5- import androidx.compose.material3.NavigationBarItem
630import androidx.compose.material3.Text
731import androidx.compose.runtime.Composable
32+ import androidx.compose.runtime.getValue
33+ import androidx.compose.runtime.remember
34+ import androidx.compose.ui.Alignment
35+ import androidx.compose.ui.Modifier
36+ import androidx.compose.ui.graphics.Color
837import androidx.compose.ui.graphics.vector.ImageVector
938import androidx.compose.ui.res.vectorResource
39+ import androidx.compose.ui.tooling.preview.Preview
40+ import androidx.compose.ui.unit.dp
41+ import com.yapp.ndgl.core.ui.theme.NDGLTheme
42+ import com.yapp.ndgl.core.ui.util.dropShadow
1043import com.yapp.ndgl.navigation.BottomNavTab
1144import com.yapp.ndgl.navigation.Route
1245
@@ -15,20 +48,88 @@ internal fun BottomNavigationBar(
1548 currentTab : Route ,
1649 onTabSelected : (Route ) -> Unit ,
1750) {
18- // FIXME 네비게이션 바 디자인 수정 및 추상화
19- NavigationBar {
20- BottomNavTab .entries.forEach { topLevelRoute ->
21- NavigationBarItem (
22- selected = currentTab == topLevelRoute.route,
23- onClick = { onTabSelected(topLevelRoute.route) },
24- icon = {
51+ val tabs = BottomNavTab .entries
52+ val selectedIndex = tabs.indexOfFirst { it.route == currentTab }
53+
54+ BoxWithConstraints (
55+ modifier = Modifier
56+ .fillMaxWidth()
57+ .navigationBarsPadding()
58+ .padding(bottom = 20 .dp)
59+ .padding(horizontal = 24 .dp)
60+ .height(68 .dp)
61+ .dropShadow(
62+ shape = RoundedCornerShape (34 .dp),
63+ color = Color .Black .copy(alpha = 0.15f ),
64+ offsetX = 1 .dp,
65+ offsetY = 6 .dp,
66+ blur = 12 .dp,
67+ )
68+ .background(NDGLTheme .colors.white, RoundedCornerShape (34 .dp))
69+ .padding(horizontal = 12 .dp, vertical = 6 .dp),
70+ ) {
71+ val totalWidth = maxWidth
72+ val selectedWidth = maxWidth * 0.56f
73+ val unselectedWidth = (totalWidth - selectedWidth) / (tabs.size - 1 )
74+ val indicatorOffset by animateDpAsState(
75+ targetValue = when (selectedIndex) {
76+ 0 -> 0 .dp
77+ 1 -> unselectedWidth
78+ else -> totalWidth - selectedWidth
79+ },
80+ animationSpec = spring(dampingRatio = Spring .DampingRatioLowBouncy , stiffness = Spring .StiffnessLow ),
81+ )
82+
83+ Box (
84+ modifier = Modifier
85+ .offset(x = indicatorOffset)
86+ .width(selectedWidth)
87+ .fillMaxHeight()
88+ .background(NDGLTheme .colors.black900, CircleShape ),
89+ )
90+
91+ Row (modifier = Modifier .fillMaxSize()) {
92+ tabs.forEach { tab ->
93+ val isSelected = currentTab == tab.route
94+
95+ Row (
96+ modifier = Modifier
97+ .width(if (isSelected) selectedWidth else unselectedWidth)
98+ .fillMaxHeight()
99+ .clickable(
100+ interactionSource = remember { MutableInteractionSource () },
101+ indication = null ,
102+ ) { onTabSelected(tab.route) },
103+ verticalAlignment = Alignment .CenterVertically ,
104+ horizontalArrangement = Arrangement .spacedBy(4 .dp, Alignment .CenterHorizontally ),
105+ ) {
25106 Icon (
26- imageVector = ImageVector .vectorResource(id = topLevelRoute.icon),
27- contentDescription = topLevelRoute.label,
107+ modifier = Modifier .size(24 .dp),
108+ imageVector = ImageVector .vectorResource(id = tab.icon),
109+ contentDescription = tab.label,
110+ tint = if (isSelected) NDGLTheme .colors.white else NDGLTheme .colors.black600,
28111 )
29- },
30- label = { Text (text = topLevelRoute.label) },
31- )
112+
113+ AnimatedVisibility (
114+ visible = isSelected,
115+ enter = fadeIn() + expandHorizontally(),
116+ exit = fadeOut() + shrinkHorizontally(),
117+ ) {
118+ Text (
119+ text = tab.label,
120+ color = NDGLTheme .colors.white,
121+ style = NDGLTheme .typography.bodyLgMedium,
122+ maxLines = 1 ,
123+ )
124+ }
125+ }
126+ }
32127 }
33128 }
34129}
130+
131+ @Preview(showBackground = true )
132+ @Composable
133+ private fun BottomNavigationBarPreview () {
134+ BottomNavigationBar (currentTab = BottomNavTab .HOME .route, onTabSelected = {})
135+ }
0 commit comments