Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2619cf2
(FEAT)[droidknights#422] RouteSessionDetail api 생성 및 기존 RouteModel 제거
s9hn Jun 5, 2025
3bfb09a
(FEAT)[droidknights#422] SessionScreen 이동 적용
s9hn Jun 5, 2025
13450c0
(FEAT)[droidknights#422] SessionDetailScreen 이동 적용
s9hn Jun 5, 2025
b50b367
(REFACTOR)[droidknights#422] SessionDetailViewModelTest, SessionListV…
s9hn Jun 5, 2025
eb61a5c
(TEST)[droidknights#422] SessionListViewModelTest 이동 테스트 추가
s9hn Jun 5, 2025
c582315
(TEST)[droidknights#422] SessionDetailViewModelTest 이동 테스트 추가
s9hn Jun 5, 2025
92b6515
(REFACTOR)[droidknights#422] MainActivity에서 navigateSessionDetail 이동 …
s9hn Jun 5, 2025
d123c17
(REFACTOR)[droidknights#422] Router 모듈의 Navigate 함수에 Navigate 파라미터 추가…
s9hn Jun 5, 2025
e98b684
(REFACTOR)[droidknights#422] 기존 Route 인터페이스 제거 및 Router-api의 Route 적용
s9hn Jun 5, 2025
08ded06
(FEAT)[droidknights#422] MainTab 이동 구현
s9hn Jun 5, 2025
fb95ac2
(REFACTOR)[droidknights#422] 사용하지 않는 Navigate 함수 제거
s9hn Jun 5, 2025
b0723f1
(REFACTOR)[droidknights#422] 테스트 함수 이름 한글화
s9hn Jun 5, 2025
40a0260
(REFACTOR)[droidknights#422] 메인탭 이동에 대한 네비게이션 옵션을 MainNavigator가 지정할 …
s9hn Jun 5, 2025
39b1776
(TEST)[droidknights#422] MainViewModelTest 탭 이동 테스트 구현
s9hn Jun 5, 2025
a640a3b
(REFACTOR)[droidknights#422] InternalLaunchedRouter 스택이 쌓이지 않는 문제 해결
s9hn Jun 5, 2025
eb67da0
(REFACTOR)[droidknights#422] RouteModel -> MainTabRoute 네이밍 수정
s9hn Jun 8, 2025
22dd11e
(REFACTOR)[droidknights#422] CI 에러 해결
s9hn Jun 8, 2025
f1a42d7
(REFACTOR)[droidknights#422] Router 모듈 단위 테스트 Navigate 파라미터 수정
s9hn Jun 9, 2025
bcb2ddb
(BUILD)[droidknights#422] resolve conflict merge
s9hn Jun 9, 2025
e68c283
(BUILD)[droidknights#422] Resolve merge conflict
s9hn Jun 10, 2025
e06ec15
(BUILD)[droidknights#422] Resolve merge conflict
s9hn Jun 10, 2025
96ee20a
(REFACTOR)[droidknights#422] SessionDetailViewModelTest, SessionListV…
s9hn Jun 5, 2025
e7fc219
(TEST)[droidknights#422] SessionListViewModelTest 이동 테스트 추가
s9hn Jun 5, 2025
da9e2d1
(TEST)[droidknights#422] SessionDetailViewModelTest 이동 테스트 추가
s9hn Jun 5, 2025
0522371
(REFACTOR)[droidknights#422] MainActivity에서 navigateSessionDetail 이동 …
s9hn Jun 5, 2025
dd27ac3
(REFACTOR)[droidknights#422] 기존 Route 인터페이스 제거 및 Router-api의 Route 적용
s9hn Jun 5, 2025
6537d19
(REFACTOR)[droidknights#422] 테스트 함수 이름 한글화
s9hn Jun 5, 2025
c7eddf6
(TEST)[droidknights#422] MainViewModelTest 탭 이동 테스트 구현
s9hn Jun 5, 2025
e5653eb
(REFACTOR)[droidknights#422] InternalLaunchedRouter 스택이 쌓이지 않는 문제 해결
s9hn Jun 5, 2025
471024a
(REFACTOR)[droidknights#422] RouteModel -> MainTabRoute 네이밍 수정
s9hn Jun 8, 2025
35dc54e
(REFACTOR)[droidknights#422] CI 에러 해결
s9hn Jun 8, 2025
ca458d9
(REFACTOR)[droidknights#422] MainViewModel, MainNavigator의 navigate 책…
s9hn Jun 10, 2025
bdc068f
(REFACTOR)[droidknights#422] Bookmark 이동 구현
s9hn Jun 10, 2025
7e0aae8
(REFACTOR)[droidknights#422] Setting 이동 구현
s9hn Jun 10, 2025
84c440d
(REFACTOR)[droidknights#422] Home 이동 구현
s9hn Jun 10, 2025
e2fff12
(BUILD)[droidknights#422] Resolve merge conflict
s9hn Jun 10, 2025
c59f0a5
(REFACTOR)[droidknights#422] 개행 추가
s9hn Jun 10, 2025
13315e5
(REFACTOR)[droidknights#422] MainTabRoute saveState, launchSingleTop …
s9hn Jun 10, 2025
644dda6
Merge branch '2025/app' into feature/#422-router
taehwandev Jun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/navigation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ android {
}

dependencies {
implementation(projects.core.router.routerApi)
implementation(libs.kotlinx.serialization.json)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package com.droidknights.app.core.navigation

import com.droidknights.app.core.router.api.model.Route
import kotlinx.serialization.Serializable

sealed interface Route {

@Serializable
data class SessionDetail(val sessionId: String) : Route
}

sealed interface MainTabRoute : Route {

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.droidknights.app.core.router.api.model.Route

interface Navigator {

suspend fun navigate(route: Route, saveState: Boolean = false)
suspend fun navigate(route: Route, saveState: Boolean = false, launchSingleTop: Boolean = false)

suspend fun navigateBack()
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ private fun InternalLaunchedRouter(

is RouteSideEffect.Navigate -> {
navHostController.navigate(sideEffect.route) {
navHostController.graph.findStartDestination().route?.let {
popUpTo(it) {
saveState = sideEffect.saveState
if (sideEffect.saveState) {
navHostController.graph.findStartDestination().route?.let {
popUpTo(it) {
saveState = true
}
}
restoreState = true
}
restoreState = sideEffect.saveState
launchSingleTop = sideEffect.launchSingleTop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import com.droidknights.app.core.router.api.model.Route

internal sealed interface InternalRoute {

data class Navigate(val route: Route, val saveState: Boolean) : InternalRoute
data class Navigate(
val route: Route,
val saveState: Boolean,
val launchSingleTop: Boolean,
) : InternalRoute

data object NavigateBack : InternalRoute
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ internal class NavigatorImpl @Inject constructor() : Navigator, InternalNavigato

override val channel = Channel<InternalRoute>(Channel.BUFFERED)

override suspend fun navigate(route: Route, saveState: Boolean) {
channel.send(InternalRoute.Navigate(route = route, saveState = saveState))
override suspend fun navigate(route: Route, saveState: Boolean, launchSingleTop: Boolean) {
channel.send(
InternalRoute.Navigate(
route = route,
saveState = saveState,
launchSingleTop = launchSingleTop,
)
)
}

override suspend fun navigateBack() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal sealed interface RouteSideEffect {
data class Navigate(
val route: Route,
val saveState: Boolean,
val launchSingleTop: Boolean,
) : RouteSideEffect

data object NavigateBack : RouteSideEffect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ internal class RouterViewModel @Inject constructor(
navigator.channel.receiveAsFlow()
.map { router ->
when (router) {
is InternalRoute.Navigate -> RouteSideEffect.Navigate(router.route, router.saveState)
is InternalRoute.Navigate -> RouteSideEffect.Navigate(
router.route,
router.saveState,
router.launchSingleTop,
)

is InternalRoute.NavigateBack -> RouteSideEffect.NavigateBack
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package com.droidknights.app.feature.bookmark.navigation

import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.droidknights.app.core.navigation.MainTabRoute
import com.droidknights.app.feature.bookmark.BookmarkRoute

fun NavController.navigateBookmark(navOptions: NavOptions) {
navigate(MainTabRoute.Bookmark, navOptions)
}

fun NavGraphBuilder.bookmarkNavGraph(
onShowErrorSnackBar: (throwable: Throwable?) -> Unit,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package com.droidknights.app.feature.contributor.navigation

import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.droidknights.app.feature.contributor.ContributorRoute
import com.droidknights.app.feature.contributor.api.RouteContributor

fun NavController.navigateContributor() {
this.navigate(RouteContributor)
}

fun NavGraphBuilder.contributorNavGraph(
onShowErrorSnackBar: (throwable: Throwable?) -> Unit,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package com.droidknights.app.feature.home.navigation

import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.droidknights.app.core.navigation.MainTabRoute
import com.droidknights.app.feature.home.HomeRoute

fun NavController.navigateHome(navOptions: NavOptions) {
navigate(MainTabRoute.Home, navOptions)
}

fun NavGraphBuilder.homeNavGraph(
padding: PaddingValues,
onShowErrorSnackBar: (throwable: Throwable?) -> Unit,
Expand Down
3 changes: 2 additions & 1 deletion feature/main/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ dependencies {
implementation(projects.feature.home)
implementation(projects.feature.setting)
implementation(projects.feature.contributor)
implementation(projects.feature.session)
implementation(projects.feature.bookmark)
implementation(projects.feature.session)
implementation(projects.feature.sessionApi)
androidTestImplementation(projects.core.testing)
debugImplementation(projects.core.uiTestHiltManifest)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,19 @@ class MainActivity : ComponentActivity() {

setContent {
val isDarkTheme by viewModel.isDarkTheme.collectAsStateWithLifecycle(false, this)

val navigator: MainNavigator = rememberMainNavigator()
val sessionId = sessionIdFromWidget.collectAsStateWithLifecycle().value
val navigator: MainNavigator = rememberMainNavigator(
onTabClick = { tab: MainTab, saveState: Boolean, launchSingleTop: Boolean ->
viewModel.navigateTab(tab.route, saveState, launchSingleTop)
}
)

// 시작지점
LaunchedRouter(navigator.navController)

LaunchedEffect(sessionId) {
sessionId?.let {
navigator.navigateSessionDetail(it)
viewModel.navigateSessionDetail(it)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.droidknights.app.core.navigation.MainTabRoute
import com.droidknights.app.core.navigation.Route
import com.droidknights.app.feature.bookmark.navigation.navigateBookmark
import com.droidknights.app.feature.home.navigation.navigateHome
import com.droidknights.app.feature.session.navigation.navigateSessionDetail
import com.droidknights.app.feature.setting.navigation.navigateSetting

internal class MainNavigator(
val navController: NavHostController,
private val onTabClick: (tab: MainTab, saveState: Boolean, launchSingleTop: Boolean) -> Unit,
) {
private val currentDestination: NavDestination?
@Composable get() = navController
Expand All @@ -30,49 +23,23 @@ internal class MainNavigator(
currentDestination?.hasRoute(tab::class) == true
}

fun navigate(tab: MainTab) {
val navOptions = navOptions {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}

when (tab) {
MainTab.SETTING -> navController.navigateSetting(navOptions)
MainTab.HOME -> navController.navigateHome(navOptions)
MainTab.BOOKMARK -> navController.navigateBookmark(navOptions)
}
}

fun navigateSessionDetail(sessionId: String) {
navController.navigateSessionDetail(sessionId)
}

private fun popBackStack() {
navController.popBackStack()
}

fun popBackStackIfNotHome() {
if (!isSameCurrentDestination<MainTabRoute.Home>()) {
popBackStack()
}
}

private inline fun <reified T : Route> isSameCurrentDestination(): Boolean {
return navController.currentDestination?.hasRoute<T>() == true
}
fun navigate(tab: MainTab) = onTabClick(tab, SAVE_STATE, LAUNCH_SINGLE_TOP)

@Composable
fun shouldShowBottomBar() = MainTab.contains {
currentDestination?.hasRoute(it::class) == true
}

companion object {
private const val SAVE_STATE = true
private const val LAUNCH_SINGLE_TOP = true
}
}

@Composable
internal fun rememberMainNavigator(
onTabClick: (tab: MainTab, saveState: Boolean, launchSingleTop: Boolean) -> Unit,
navController: NavHostController = rememberNavController(),
): MainNavigator = remember(navController) {
MainNavigator(navController)
MainNavigator(navController, onTabClick)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 라우터 작업은 이 부분을 제거하기 위함이긴 합니다.

view > viewModel에서 각각의 navigate 처리를 하기 위함.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 태환님 리뷰 감사드립니다.
구현을 고민해보며 MainNavigator에서 MainViewModel을 통한 Navigate가 아닌,
탭 선택 시 상태를 각 스크린에 전파하고 각 스크린의 뷰모델이 Navigate 책임을 이행하도록 수정했습니다.
MainNavigator는 내부적으로 NavController와 MainTab을 활용해 탭 네비게이팅 관련 API를 잘 캡슐화하고 있다고 판단해 그대로 두었습니다.

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import java.net.UnknownHostException

@Composable
internal fun MainScreen(
navigator: MainNavigator = rememberMainNavigator(),
navigator: MainNavigator,
onChangeDarkTheme: (Boolean) -> Unit,
) {
val snackBarHostState = remember { SnackbarHostState() }
Expand Down Expand Up @@ -71,7 +71,7 @@ private fun MainScreenContent(
visible = navigator.shouldShowBottomBar(),
tabs = MainTab.entries.toPersistentList(),
currentTab = navigator.currentTab,
onTabSelected = { navigator.navigate(it) }
onTabSelected = navigator::navigate,
)
},
snackbarHost = { SnackbarHost(snackBarHostState) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.droidknights.app.feature.main

import androidx.compose.runtime.Composable
import com.droidknights.app.core.navigation.MainTabRoute
import com.droidknights.app.core.navigation.Route
import com.droidknights.app.core.router.api.model.Route

internal enum class MainTab(
val iconResId: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package com.droidknights.app.feature.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.droidknights.app.core.data.settings.api.SettingsRepository
import com.droidknights.app.core.router.api.Navigator
import com.droidknights.app.core.router.api.model.Route
import com.droidknights.app.feature.session.api.RouteSessionDetail
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
internal class MainViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val navigator: Navigator,
) : ViewModel() {

val isDarkTheme = settingsRepository.flowIsDarkTheme()
Expand All @@ -18,4 +22,17 @@ class MainViewModel @Inject constructor(
viewModelScope.launch {
settingsRepository.updateIsDarkTheme(isDarkTheme)
}

fun navigateSessionDetail(sessionId: String) = viewModelScope.launch {
navigator.navigate(RouteSessionDetail(sessionId))
}

fun navigateTab(route: Route, saveState: Boolean, launchSingleTop: Boolean) =
viewModelScope.launch {
navigator.navigate(
route = route,
saveState = saveState,
launchSingleTop = launchSingleTop,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ internal fun MainNavHost(
)

sessionNavGraph(
onBackClick = navigator::popBackStackIfNotHome,
onSessionClick = { navigator.navigateSessionDetail(it.id) },
onShowErrorSnackBar = onShowErrorSnackBar
)
}
Expand Down
Loading
Loading