-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PC-185] Bottom Navigation Bar UI 구현 #16
Changes from all commits
6eaca3c
f80f29c
4c73d4e
9a7410f
4fee7de
6f925ce
e36bec3
7455517
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,45 @@ | ||
@file:OptIn(ExperimentalMaterial3Api::class) | ||
|
||
package com.puzzle.piece.ui | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.navigationBarsPadding | ||
import androidx.compose.foundation.layout.offset | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material.BottomNavigation | ||
import androidx.compose.material.BottomNavigationItem | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.shape.CircleShape | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.FabPosition | ||
import androidx.compose.material3.FloatingActionButton | ||
import androidx.compose.material3.FloatingActionButtonDefaults.bottomAppBarFabElevation | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.NavigationBar | ||
import androidx.compose.material3.NavigationBarItem | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.unit.dp | ||
import androidx.navigation.NavDestination | ||
import androidx.navigation.NavDestination.Companion.hasRoute | ||
import androidx.navigation.NavDestination.Companion.hierarchy | ||
import androidx.navigation.NavHostController | ||
import androidx.navigation.compose.currentBackStackEntryAsState | ||
import com.puzzle.common.ui.NoRippleInteractionSource | ||
import com.puzzle.designsystem.foundation.PieceTheme | ||
import com.puzzle.navigation.AuthGraph | ||
import com.puzzle.navigation.EtcRoute | ||
import com.puzzle.navigation.MatchingGraph | ||
import com.puzzle.navigation.MatchingGraphDest.MatchingDetailRoute | ||
import com.puzzle.navigation.MyPageRoute | ||
import com.puzzle.navigation.Route | ||
import com.puzzle.navigation.SettingRoute | ||
import com.puzzle.piece.R | ||
import com.puzzle.piece.navigation.AppNavHost | ||
import com.puzzle.piece.navigation.TopLevelDestination | ||
import kotlin.reflect.KClass | ||
|
@@ -42,7 +62,25 @@ fun App( | |
navigateToTopLevelDestination = navigateToTopLevelDestination, | ||
) | ||
} | ||
} | ||
}, | ||
floatingActionButton = { | ||
if (currentDestination?.shouldHideBottomNavigation() == false) { | ||
FloatingActionButton( | ||
onClick = { navigateToTopLevelDestination(MatchingGraph) }, | ||
containerColor = PieceTheme.colors.white, | ||
shape = CircleShape, | ||
elevation = bottomAppBarFabElevation(), | ||
modifier = Modifier.offset(y = 84.dp), | ||
) { | ||
Image( | ||
painter = painterResource(R.drawable.ic_matching), | ||
contentDescription = null, | ||
modifier = Modifier.size(80.dp), | ||
) | ||
} | ||
} | ||
}, | ||
floatingActionButtonPosition = FabPosition.Center, | ||
) { innerPadding -> | ||
val contentModifier = modifier.padding(innerPadding) | ||
|
||
|
@@ -58,24 +96,51 @@ private fun AppBottomBar( | |
currentDestination: NavDestination?, | ||
navigateToTopLevelDestination: (Route) -> Unit, | ||
) { | ||
BottomNavigation( | ||
modifier = Modifier.navigationBarsPadding() | ||
NavigationBar( | ||
containerColor = PieceTheme.colors.white, | ||
modifier = Modifier | ||
.navigationBarsPadding() | ||
.height(68.dp), | ||
) { | ||
TopLevelDestination.topLevelDestinations.forEach { topLevelRoute -> | ||
BottomNavigationItem( | ||
NavigationBarItem( | ||
icon = { | ||
Icon( | ||
imageVector = topLevelRoute.selectedIcon, | ||
contentDescription = topLevelRoute.name | ||
) | ||
Column( | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
modifier = Modifier.padding(top = 2.dp), | ||
) { | ||
Icon( | ||
painter = painterResource(topLevelRoute.iconDrawableId), | ||
contentDescription = topLevelRoute.contentDescription, | ||
modifier = Modifier.size(32.dp), | ||
) | ||
|
||
Text( | ||
text = topLevelRoute.title, | ||
style = PieceTheme.typography.captionM, | ||
) | ||
} | ||
}, | ||
label = { Text(topLevelRoute.name) }, | ||
alwaysShowLabel = false, | ||
selected = currentDestination.isRouteInHierarchy(topLevelRoute.route), | ||
colors = androidx.compose.material3.NavigationBarItemDefaults.colors( | ||
selectedIconColor = PieceTheme.colors.primaryDefault, | ||
unselectedIconColor = PieceTheme.colors.dark3, | ||
selectedTextColor = PieceTheme.colors.primaryDefault, | ||
unselectedTextColor = PieceTheme.colors.dark3, | ||
indicatorColor = Color.Transparent, | ||
), | ||
interactionSource = remember { NoRippleInteractionSource() }, | ||
onClick = { | ||
when (topLevelRoute) { | ||
TopLevelDestination.MATCHING -> navigateToTopLevelDestination(MatchingGraph) | ||
TopLevelDestination.MATCHING -> navigateToTopLevelDestination( | ||
MatchingGraph | ||
) | ||
|
||
TopLevelDestination.MY_PAGE -> navigateToTopLevelDestination(MyPageRoute) | ||
TopLevelDestination.ETC -> navigateToTopLevelDestination(EtcRoute) | ||
TopLevelDestination.SETTING -> navigateToTopLevelDestination( | ||
SettingRoute | ||
) | ||
} | ||
}, | ||
) | ||
|
@@ -85,21 +150,11 @@ private fun AppBottomBar( | |
|
||
private val HIDDEN_BOTTOM_NAV_ROUTES = setOf( | ||
AuthGraph::class.qualifiedName, | ||
MatchingDetailRoute::class.qualifiedName | ||
MatchingDetailRoute::class.qualifiedName, | ||
) | ||
|
||
/** | ||
* 현재 목적지가 바텀 네비게이션이 보여지지 않는 화면인지 확인하는 메서드 | ||
*/ | ||
private fun NavDestination?.shouldHideBottomNavigation(): Boolean = | ||
this?.hierarchy?.any { destination -> | ||
destination.route in HIDDEN_BOTTOM_NAV_ROUTES | ||
} ?: false | ||
this?.hierarchy?.any { destination -> destination.route in HIDDEN_BOTTOM_NAV_ROUTES } ?: false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p4 : this?.hierarchy?.any { it.route in HIDDEN_BOTTOM_NAV_ROUTES } == true 으로 변경하면 좋을 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 조아용! |
||
|
||
/** | ||
* 현재 목적지가 TopLevelDestination 라우트에 속하는지 확인하는 메서드 | ||
*/ | ||
private fun NavDestination?.isRouteInHierarchy(route: KClass<*>): Boolean = | ||
this?.hierarchy?.any { | ||
it.hasRoute(route) | ||
} ?: false | ||
this?.hierarchy?.any { it.hasRoute(route) } == true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="80dp" | ||
android:height="80dp" | ||
android:viewportWidth="80" | ||
android:viewportHeight="80"> | ||
<path | ||
android:pathData="M40,3L40,3A37,37 0,0 1,77 40L77,40A37,37 0,0 1,40 77L40,77A37,37 0,0 1,3 40L3,40A37,37 0,0 1,40 3z" | ||
android:strokeWidth="6" | ||
android:fillColor="#6F00FB" | ||
android:strokeColor="#ffffff"/> | ||
<path | ||
android:pathData="M56.667,34.713C56.667,42.784 49.967,47.084 45.064,50.934C43.334,52.292 41.667,53.571 40.001,53.571C40.001,53.571 40.001,51.059 40.001,49.392C40.001,47.726 40.001,46.403 40.001,38.333C40.001,30.264 40.001,28.678 40.001,28.678C47.501,20.918 56.667,26.642 56.667,34.713Z" | ||
android:fillColor="#D0ABFD"/> | ||
<path | ||
android:pathData="M33.508,49.829C28.829,46.238 23.334,42.022 23.334,34.713C23.334,26.691 32.389,20.987 39.863,28.538C39.95,28.626 40.001,28.747 40.001,28.871V36.228C40.001,36.705 40.775,36.993 41.199,36.773C41.625,36.553 42.108,36.429 42.62,36.429C44.329,36.429 45.715,37.814 45.715,39.524C45.715,41.233 44.329,42.619 42.62,42.619C42.108,42.619 41.625,42.495 41.199,42.275C40.775,42.055 40.001,42.343 40.001,42.82V49.392V53.095C40.001,53.358 39.786,53.574 39.526,53.538C38.017,53.328 36.503,52.163 34.937,50.934C34.474,50.571 33.996,50.203 33.508,49.829Z" | ||
android:fillColor="#F6EFFF"/> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="33dp" | ||
android:height="32dp" | ||
android:viewportWidth="33" | ||
android:viewportHeight="32"> | ||
<path | ||
android:pathData="M10.25,11H21.75" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599" | ||
android:strokeLineCap="round"/> | ||
<path | ||
android:pathData="M10.25,16H21.75" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599" | ||
android:strokeLineCap="round"/> | ||
<path | ||
android:pathData="M10.25,21H18.25" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599" | ||
android:strokeLineCap="round"/> | ||
<path | ||
android:pathData="M7.25,6L25.25,6A1,1 0,0 1,26.25 7L26.25,25A1,1 0,0 1,25.25 26L7.25,26A1,1 0,0 1,6.25 25L6.25,7A1,1 0,0 1,7.25 6z" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599"/> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="33dp" | ||
android:height="32dp" | ||
android:viewportWidth="33" | ||
android:viewportHeight="32"> | ||
<path | ||
android:pathData="M13.388,7.209C13.388,6.173 14.27,5.333 15.358,5.333H18.142C19.23,5.333 20.113,6.173 20.113,7.209C20.113,8.652 21.755,9.555 23.069,8.833C24.011,8.315 25.217,8.622 25.761,9.519L27.153,11.813C27.697,12.71 27.374,13.857 26.431,14.375C25.117,15.097 25.117,16.902 26.431,17.624C27.374,18.142 27.697,19.289 27.153,20.186L25.761,22.48C25.217,23.377 24.011,23.684 23.069,23.166C21.755,22.444 20.113,23.347 20.113,24.791C20.113,25.827 19.23,26.666 18.142,26.666H15.358C14.27,26.666 13.388,25.827 13.388,24.791C13.388,23.347 11.745,22.444 10.432,23.166C9.489,23.684 8.284,23.377 7.74,22.48L6.348,20.186C5.804,19.289 6.127,18.142 7.069,17.624C8.383,16.902 8.383,15.097 7.069,14.375C6.127,13.857 5.804,12.71 6.348,11.813L7.74,9.519C8.284,8.622 9.489,8.315 10.432,8.833C11.745,9.555 13.388,8.652 13.388,7.209Z" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599"/> | ||
<path | ||
android:pathData="M21.233,16C21.233,18.356 19.226,20.266 16.75,20.266C14.274,20.266 12.267,18.356 12.267,16C12.267,13.643 14.274,11.733 16.75,11.733C19.226,11.733 21.233,13.643 21.233,16Z" | ||
android:strokeWidth="1.6" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#909599"/> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.puzzle.common.ui | ||
|
||
import androidx.compose.foundation.interaction.Interaction | ||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.runtime.Immutable | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.emptyFlow | ||
|
||
@Immutable | ||
class NoRippleInteractionSource : MutableInteractionSource { | ||
override suspend fun emit(interaction: Interaction) {} | ||
override val interactions: Flow<Interaction> = emptyFlow() | ||
override fun tryEmit(interaction: Interaction): Boolean = true | ||
} | ||
Comment on lines
+9
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이쪽에 두셨군요! 저도 matching ui에서 썼던 CollapsingHeaderNestedScrollConnection 클래스를 어디 둘까 고민하고 있는데 ui에 두면 좋을까요?! 🤔 태규님 의견이 궁금합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
아하, 생각해보니 별 생각 없이 뒀는데 최소 3군데 이상 다른 모듈에서 쓰이면 NoRippleInteractionSource 도 일단은 app모듈에서만 사용하니까 app모듈로 돌려두겠습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 죠습니다!! |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -275,4 +275,4 @@ private fun PreviewMatchingScreen() { | |
navigateToMatchingDetail = {}, | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,5 @@ plugins { | |
} | ||
|
||
android { | ||
namespace = "com.puzzle.etc" | ||
namespace = "com.puzzle.setting" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
캬아... 멋지십니다 👍 👍