Skip to content

Commit ca1c39f

Browse files
authored
Merge pull request #7 from YAPP-Github/init/#3-navigation3-setup
[Init] #3 Nav3 셋업
2 parents ce2e7eb + 7538094 commit ca1c39f

54 files changed

Lines changed: 812 additions & 110 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/build.gradle.kts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
alias(libs.plugins.neki.android.application)
3+
alias(libs.plugins.neki.android.application.compose)
34
}
45

56
android {
@@ -14,19 +15,21 @@ dependencies {
1415
implementation(projects.core.common)
1516
implementation(projects.core.dataApi)
1617
implementation(projects.core.data)
18+
implementation(projects.core.designsystem)
1719
implementation(projects.core.domain)
1820
implementation(projects.core.model)
19-
implementation(projects.core.designsystem)
21+
implementation(projects.core.navigation)
2022
implementation(projects.feature.sample.impl)
2123
implementation(projects.feature.sample.api)
22-
23-
implementation(projects.core.common)
24-
implementation(projects.core.data)
25-
implementation(projects.core.dataApi)
26-
implementation(projects.core.designsystem)
27-
implementation(projects.core.domain)
28-
implementation(projects.core.model)
24+
implementation(projects.feature.pose.api)
25+
implementation(projects.feature.pose.impl)
26+
implementation(projects.feature.archive.api)
27+
implementation(projects.feature.archive.impl)
28+
implementation(projects.feature.map.api)
29+
implementation(projects.feature.map.impl)
2930

3031
implementation(libs.timber)
3132

33+
implementation(libs.androidx.activity.compose)
34+
implementation(libs.androidx.navigation3.ui)
3235
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
android:theme="@style/Theme.Neki">
1616

1717
<activity
18-
android:name="com.neki.android.feature.sample.impl.MainActivity"
18+
android:name=".MainActivity"
1919
android:exported="true">
2020
<intent-filter>
2121
<action android:name="android.intent.action.MAIN" />
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.neki.android.app
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.activity.enableEdgeToEdge
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.material3.Scaffold
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.runtime.mutableStateOf
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.ui.Modifier
14+
import androidx.navigation3.runtime.entryProvider
15+
import androidx.navigation3.ui.NavDisplay
16+
import com.neki.android.app.ui.BottomNavigationBar
17+
import com.neki.android.core.designsystem.ui.theme.NekiTheme
18+
import com.neki.android.core.navigation.EntryProviderInstaller
19+
import com.neki.android.core.navigation.NavigatorImpl
20+
import com.neki.android.core.navigation.toEntries
21+
import dagger.hilt.android.AndroidEntryPoint
22+
import javax.inject.Inject
23+
24+
@AndroidEntryPoint
25+
class MainActivity : ComponentActivity() {
26+
27+
@Inject
28+
lateinit var navigator: NavigatorImpl
29+
30+
@Inject
31+
lateinit var entryProviderScopes: Set<@JvmSuppressWildcards EntryProviderInstaller>
32+
33+
override fun onCreate(savedInstanceState: Bundle?) {
34+
super.onCreate(savedInstanceState)
35+
enableEdgeToEdge()
36+
setContent {
37+
val shouldShowBottomBar by remember(navigator.state.currentKey) {
38+
mutableStateOf(navigator.state.currentKey in navigator.state.topLevelKeys)
39+
}
40+
41+
NekiTheme {
42+
Scaffold(
43+
modifier = Modifier.fillMaxSize(),
44+
bottomBar = {
45+
BottomNavigationBar(
46+
visible = shouldShowBottomBar,
47+
currentTab = navigator.state.currentTopLevelKey,
48+
currentKey = navigator.state.currentKey,
49+
onTabSelected = { navigator.navigate(it.navKey) },
50+
)
51+
}
52+
) { innerPadding ->
53+
NavDisplay(
54+
modifier = Modifier.padding(innerPadding),
55+
entries = navigator.state.toEntries(
56+
entryProvider = entryProvider {
57+
entryProviderScopes.forEach { builder -> this.builder() }
58+
},
59+
),
60+
onBack = { navigator.goBack() },
61+
)
62+
}
63+
}
64+
}
65+
}
66+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.neki.android.app.navigation
2+
3+
import androidx.annotation.DrawableRes
4+
import androidx.annotation.StringRes
5+
import androidx.navigation3.runtime.NavKey
6+
import com.neki.android.app.R
7+
import com.neki.android.feature.archive.api.ArchiveNavKey
8+
import com.neki.android.feature.map.api.MapNavKey
9+
import com.neki.android.feature.pose.api.PoseNavKey
10+
11+
enum class TopLevelNavItem(
12+
@DrawableRes val selectedIcon: Int,
13+
@DrawableRes val unselectedIcon: Int,
14+
@StringRes val iconTextId: Int,
15+
val navKey: NavKey,
16+
) {
17+
POSE_RECOMMEND(
18+
selectedIcon = R.drawable.ic_nav_pose_selected,
19+
unselectedIcon = R.drawable.ic_nav_pose_unselected,
20+
iconTextId = R.string.top_level_nav_pose,
21+
navKey = PoseNavKey.Pose,
22+
),
23+
ARCHIVE(
24+
selectedIcon = R.drawable.ic_nav_archive_selected,
25+
unselectedIcon = R.drawable.ic_nav_archive_unselected,
26+
iconTextId = R.string.top_level_nav_archive,
27+
navKey = ArchiveNavKey.Archive
28+
),
29+
MAP(
30+
selectedIcon = R.drawable.ic_nav_map_selected,
31+
unselectedIcon = R.drawable.ic_nav_map_unselected,
32+
iconTextId = R.string.top_level_nav_map,
33+
navKey = MapNavKey.Map
34+
);
35+
36+
companion object {
37+
val startTopLevelItem = ARCHIVE
38+
}
39+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.neki.android.app.navigation.di
2+
3+
import com.neki.android.core.navigation.Navigator
4+
import com.neki.android.core.navigation.NavigatorImpl
5+
import dagger.Binds
6+
import dagger.Module
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.android.components.ActivityRetainedComponent
9+
10+
@Module
11+
@InstallIn(ActivityRetainedComponent::class)
12+
internal interface AppModule {
13+
14+
@Binds
15+
fun bindsNavigator(
16+
impl: NavigatorImpl,
17+
): Navigator
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.neki.android.app.navigation.di
2+
3+
import com.neki.android.app.navigation.keys.START_NAV_KEY
4+
import com.neki.android.app.navigation.keys.TOP_LEVEL_NAV_KEYS
5+
import com.neki.android.core.navigation.NavigationState
6+
import dagger.Module
7+
import dagger.Provides
8+
import dagger.hilt.InstallIn
9+
import dagger.hilt.android.components.ActivityRetainedComponent
10+
import dagger.hilt.android.scopes.ActivityRetainedScoped
11+
12+
@Module
13+
@InstallIn(ActivityRetainedComponent::class)
14+
internal object NavigationModule {
15+
16+
@Provides
17+
@ActivityRetainedScoped
18+
fun providesNavigationState(): NavigationState {
19+
return NavigationState(
20+
startKey = START_NAV_KEY,
21+
topLevelKeys = TOP_LEVEL_NAV_KEYS.toSet(),
22+
)
23+
}
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.neki.android.app.navigation.keys
2+
3+
import com.neki.android.app.navigation.TopLevelNavItem
4+
import com.neki.android.feature.archive.api.ArchiveNavKey
5+
6+
internal val START_NAV_KEY = ArchiveNavKey.Archive
7+
internal val TOP_LEVEL_NAV_KEYS = TopLevelNavItem.entries.map { it.navKey }
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.neki.android.app.ui
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.slideInVertically
5+
import androidx.compose.animation.slideOutVertically
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.navigationBarsPadding
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.material3.Icon
13+
import androidx.compose.material3.Surface
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.getValue
17+
import androidx.compose.runtime.mutableStateOf
18+
import androidx.compose.runtime.remember
19+
import androidx.compose.runtime.setValue
20+
import androidx.compose.ui.Alignment
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.graphics.Color
23+
import androidx.compose.ui.graphics.vector.ImageVector
24+
import androidx.compose.ui.res.stringResource
25+
import androidx.compose.ui.res.vectorResource
26+
import androidx.compose.ui.tooling.preview.Preview
27+
import androidx.compose.ui.unit.dp
28+
import androidx.navigation3.runtime.NavKey
29+
import com.neki.android.app.navigation.TopLevelNavItem
30+
import com.neki.android.core.designsystem.ui.theme.NekiTheme
31+
32+
@Composable
33+
fun BottomNavigationBar(
34+
visible: Boolean,
35+
currentKey: NavKey,
36+
currentTab: NavKey,
37+
tabs: List<TopLevelNavItem> = TopLevelNavItem.entries,
38+
onTabSelected: (TopLevelNavItem) -> Unit,
39+
) {
40+
AnimatedVisibility(
41+
visible = visible,
42+
enter = slideInVertically { it },
43+
exit = slideOutVertically { it },
44+
) {
45+
Surface(
46+
modifier = Modifier
47+
.navigationBarsPadding()
48+
.fillMaxWidth(),
49+
) {
50+
Row(
51+
modifier = Modifier
52+
.fillMaxWidth()
53+
.padding(horizontal = 20.dp),
54+
horizontalArrangement = Arrangement.spacedBy(2.5.dp)
55+
) {
56+
tabs.forEach { tab ->
57+
BottomNavigationBarItem(
58+
modifier = Modifier.weight(1f),
59+
selected = tab.navKey == currentTab,
60+
tab = tab,
61+
onClick = { if (tab.navKey != currentKey) onTabSelected(tab) }
62+
)
63+
}
64+
}
65+
}
66+
}
67+
}
68+
69+
@Composable
70+
fun BottomNavigationBarItem(
71+
selected: Boolean,
72+
tab: TopLevelNavItem,
73+
modifier: Modifier = Modifier,
74+
onClick: () -> Unit = {},
75+
) {
76+
val icon = if (selected) tab.selectedIcon else tab.unselectedIcon
77+
val color = if (selected) Color(0xFF3C3E48) else Color(0xFFB7B9C3)
78+
79+
Surface(
80+
modifier = modifier,
81+
onClick = onClick
82+
) {
83+
Column(
84+
modifier = Modifier.padding(vertical = 8.dp),
85+
horizontalAlignment = Alignment.CenterHorizontally,
86+
verticalArrangement = Arrangement.spacedBy(8.dp),
87+
) {
88+
Icon(
89+
imageVector = ImageVector.vectorResource(icon),
90+
contentDescription = stringResource(tab.iconTextId),
91+
tint = color,
92+
)
93+
Text(
94+
text = stringResource(tab.iconTextId),
95+
color = color,
96+
)
97+
}
98+
}
99+
}
100+
101+
@Preview
102+
@Composable
103+
private fun BottomNavigationBarPreview() {
104+
var currentTab by remember { mutableStateOf(TopLevelNavItem.ARCHIVE) }
105+
NekiTheme {
106+
BottomNavigationBar(
107+
visible = true,
108+
tabs = TopLevelNavItem.entries,
109+
currentTab = currentTab.navKey,
110+
currentKey = currentTab.navKey,
111+
) { currentTab = it }
112+
}
113+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="28dp"
3+
android:height="28dp"
4+
android:viewportWidth="28"
5+
android:viewportHeight="28">
6+
<path
7+
android:pathData="M23.333,2.334H7C5.593,2.334 3.5,3.266 3.5,5.834V22.167C3.5,24.735 5.593,25.667 7,25.667H24.5V23.334H7.014C6.475,23.32 5.833,23.108 5.833,22.167C5.833,21.227 6.475,21.015 7.014,21.001H24.5V3.501C24.5,3.191 24.377,2.894 24.158,2.676C23.94,2.457 23.643,2.334 23.333,2.334ZM11.087,5.834C11.552,5.834 11.998,6.019 12.327,6.348C12.656,6.676 12.84,7.122 12.84,7.587C12.84,8.053 12.656,8.499 12.327,8.827C11.998,9.156 11.552,9.341 11.087,9.341C10.622,9.341 10.176,9.156 9.847,8.827C9.518,8.499 9.333,8.053 9.333,7.587C9.333,7.122 9.518,6.676 9.847,6.348C10.176,6.019 10.622,5.834 11.087,5.834ZM14,15.167H8.167L11.667,11.667L13.417,13.3L16.917,9.334L21,15.167H14Z"
8+
android:fillColor="#3C3E48"/>
9+
</vector>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="28dp"
3+
android:height="28dp"
4+
android:viewportWidth="28"
5+
android:viewportHeight="28">
6+
<path
7+
android:pathData="M23.333,2.334H7C5.593,2.334 3.5,3.266 3.5,5.834V22.167C3.5,24.735 5.593,25.667 7,25.667H24.5V23.334H7.014C6.475,23.32 5.833,23.108 5.833,22.167C5.833,21.227 6.475,21.015 7.014,21.001H24.5V3.501C24.5,3.191 24.377,2.894 24.158,2.676C23.94,2.457 23.643,2.334 23.333,2.334ZM11.087,5.834C11.552,5.834 11.998,6.019 12.327,6.348C12.656,6.676 12.84,7.122 12.84,7.587C12.84,8.053 12.656,8.499 12.327,8.827C11.998,9.156 11.552,9.341 11.087,9.341C10.622,9.341 10.176,9.156 9.847,8.827C9.518,8.499 9.333,8.053 9.333,7.587C9.333,7.122 9.518,6.676 9.847,6.348C10.176,6.019 10.622,5.834 11.087,5.834ZM14,15.167H8.167L11.667,11.667L13.417,13.3L16.917,9.334L21,15.167H14Z"
8+
android:fillColor="#B7B9C3"/>
9+
</vector>

0 commit comments

Comments
 (0)