Skip to content

Navigation3 기반 앱 네비게이션 시스템 구축 및 Scaffold 기본 구조 구현#51

Open
HamBeomJoon wants to merge 30 commits intodevelopfrom
feat/#48-prezel-app-state
Open

Navigation3 기반 앱 네비게이션 시스템 구축 및 Scaffold 기본 구조 구현#51
HamBeomJoon wants to merge 30 commits intodevelopfrom
feat/#48-prezel-app-state

Conversation

@HamBeomJoon
Copy link
Contributor

@HamBeomJoon HamBeomJoon commented Feb 6, 2026

📌 작업 내용

📍 1. Navigation3 기반 핵심 네비게이션 시스템 추가

  • core:navigation 모듈 생성
  • Navigation3 의존성 설정(navigation3.runtime, navigation3.ui, savedstate-compose)
  • Navigator 클래스 추가
    • 상위 레벨 전환, 서브 스택 관리, 뒤로가기 로직 처리
  • NavigationState + rememberNavigationState 구현
    • Compose 환경에서 네비게이션 상태 유지
  • toEntries(...) 확장 함수 추가
    • NavigationState → NavEntry 리스트 변환

📍 2. Feature 모듈 분리 (API/Impl) 구조 도입
Home, History, Profile 피처를 각각 api/impl 모듈로 분리
모듈별 NavKey 및 기본 구현 추가
feature:home, feature:history, feature:profile 모듈들이 독립적으로 구성됨

📍 3. PrezelBottomNavigationBar 컴포넌트 추가

  • PrezelNavigationBar, PrezelNavigationBarItem 컴포넌트 추가
    • 디자인 테마(PrezelTheme)에 맞춘 스타일 적용
  • PrezelNavigationScaffold 및 PrezelNavigationScope DSL 추가

📍 4. App 구조 변경 (Compose + Hilt)

  • PrezelAppState 및 rememberPrezelAppState 추가
    • 네비게이션 상태 + 네트워크 모니터링 저장
  • PrezelApp 최상위 Composable 추가
    • NavDisplay 기반 화면 전환 구조 설정
  • MainActivity에서 Hilt 적용 후 PrezelApp 실행 로직 구성

📍 5. 네트워크 상태 모니터링 기능 추가

  • NetworkMonitor 인터페이스 및 구현체 (ConnectivityManagerNetworkMonitor)
  • DI 설정 추가 (DataModule, 권한 ACCESS_NETWORK_STATE)

📍 6. 빌드/디자인 시스템 변경

  • PrezelDispatchers + Hilt Coroutine DI 설정 추가
  • 디자인 시스템 업데이트:
    • 네비게이션 바 배경, 아이콘 색상
    • 상단 구분선(border) 추가

📍 7. 네비게이션 구조 리팩토링

  • EntryBuilder + Hilt Multi-bindings 패턴 도입
    • 각 feature가 스스로 네비게이션 엔트리를 등록
  • 네비게이션 전환 애니메이션 제거 설정
  • 여러 유틸리티 함수 및 state 접근성, 확장 함수 개선

🧩 관련 이슈


📸 스크린샷

  • BottomNavigationBar
Screenshot_1770362512 Screenshot_1770362515 Screenshot_1770362517

📢 논의하고 싶은 내용

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 하단 네비게이션(홈/히스토리/프로필) UI 추가 및 한국어 라벨(홈/히스토리/프로필) 적용
    • 홈/히스토리/프로필 모듈형 화면과 내비게이션 항목의 자동 등록 지원
    • 앱 전역 오프라인/온라인 상태 노출을 위한 네트워크 연결 감지 제공
  • 리팩터

    • 앱 전반의 내비게이션·앱 상태 관리 구조 도입 및 DI 기반 모듈화 강화
  • 스타일

    • 체크박스 선택 시 색상 조정 (피드백 색상으로 변경)

Navigation3 라이브러리를 활용하여 다중 백스택(Multiple Back Stacks)을 지원하는 네비게이션 구조를 설계하고 구현했습니다.

*   `Navigator`: 상위 레벨 전환, 서브 스택 관리 및 뒤로 가기 로직을 처리하는 클래스 추가
*   `NavigationState`: `NavBackStack`을 활용한 상태 관리 및 `rememberNavigationState` 구현
*   `toEntries`: `NavigationState`를 `NavHost`에서 사용 가능한 `NavEntry` 리스트로 변환하는 확장 함수 추가
*   `core:navigation` 모듈 설정 및 관련 의존성(`navigation3`, `savedstate-compose`) 추가
home feature의 인터페이스와 네비게이션 정의를 위한 `feature:home:api` 모듈을 생성했습니다.

*   `feature:home:api` 모듈 및 관련 설정 파일 추가
*   `HomeNavKey` 정의 및 `navigation3` 의존성 추가
*   `libs.versions.toml`에 appcompat, material, navigation3 버전 정보 추가
*   `settings.gradle.kts`에 신규 모듈 등록
home feature의 구현(impl) 모듈을 생성하고 기본 구조를 설정했습니다.

*   `home:impl` 모듈 생성 및 `settings.gradle.kts` 등록
*   `HomeScreen`, `HomeViewModel`, `HomeUiState` 기본 코드 추가
*   `navigation3`를 이용한 `HomeEntryProvider` 네비게이션 설정 추가
*   `AndroidFeatureImplConventionPlugin` 내 불필요한 의존성 주석 처리
*   `TopLevelNavItem` 데이터 클래스 및 홈, 히스토리, 프로필 아이템 정의
*   `app` 모듈에 내비게이션 및 홈 피처 관련 의존성 추가
*   `feature:home:api`에 타이틀 문자열 리소스 추가
의존성 주입을 위한 Coroutine Dispatcher 및 ApplicationScope 설정을 추가합니다.

*   `PrezelDispatchers` enum 및 `@Dispatcher` 한정자(Qualifier) 정의
*   `DispatchersModule`을 통해 IO, Default 디스패처 제공
*   `CoroutineScopesModule`을 통해 `SupervisorJob` 기반의 `ApplicationScope` 제공
네트워크 연결 상태를 실시간으로 감지하기 위한 `NetworkMonitor` 인터페이스 및 구현체를 추가했습니다.

* `NetworkMonitor` 인터페이스 및 `ConnectivityManagerNetworkMonitor` 구현체 추가
* `DataModule`을 통한 `NetworkMonitor` 의존성 주입 설정
* `RepositoryModule` 위치 변경 (`com.team.prezel.core.data` -> `com.team.prezel.core.data.di`)
* `AndroidManifest.xml`에 `ACCESS_NETWORK_STATE` 권한 추가
Navigation3 라이브러리를 사용하여 앱의 네비게이션 구조를 설정하고, `HomeNavKey`를 통한 홈 화면 진입을 구현했습니다.

*   `PrezelAppState` 및 `rememberPrezelAppState`를 추가하여 네비게이션 상태와 네트워크 모니터링 관리
*   `PrezelApp` 컴포저블을 추가하여 `NavDisplay` 기반의 화면 전환 구조 정의
*   `MainActivity`에 Hilt를 적용하고 `PrezelApp`을 시작 화면으로 설정
*   `TopLevelNavItem`에 `TOP_LEVEL_KEYS` 및 `START_KEY` 정의
*   `HomeScreen`에 기본적인 UI 구성 요소 추가
*   `navigation3-ui` 의존성 및 `kotlinx-serialization` 플러그인 추가
디자인 시스템에 `PrezelNavigationBar` 및 `PrezelNavigationBarItem` 공통 컴포넌트를 추가했습니다.

*   `NavigationBar`를 기반으로 한 `PrezelNavigationBar` 구현
*   아이콘, 라벨, 선택 상태에 따른 스타일이 적용된 `PrezelNavigationBarItem` 구현
*   디자인 시스템 테마(PrezelTheme) 및 색상, 타이포그래피 적용
하단 네비게이션 바를 포함한 화면 구조를 쉽게 구성할 수 있도록 `PrezelNavigationScaffold`와 이를 위한 DSL 형태의 `PrezelNavigationScope`를 추가했습니다.

*   `PrezelNavigationScaffold` 컴포저블 추가
*   `PrezelNavigationScope`를 통한 네비게이션 아이템 선언 방식 도입
*   관련 프리뷰 코드 업데이트 및 Scaffold 적용 방식으로 수정
- `PrezelNavigationScaffold`를 도입하여 네비게이션 바 레이아웃을 적용했습니다.
- `NavDisplay`에 `entryProvider`를 사용하여 화면 전환 로직을 리팩토링했습니다.
- `NavigationState`에 현재 최상위 키를 반환하는 `currentTopLevelKeyOrStart` 확장 함수를 추가했습니다.
- `NavigationState`의 `currentSubStack` 및 `currentKey` 프로퍼티의 접근 제한을 완화했습니다.
Android Feature 및 Navigation 관련 모듈의 의존성 구조를 정리하고, 중복된 설정을 컨벤션 플러그인으로 통합했습니다.

*   `AndroidFeatureImplConventionPlugin`: `library.compose` 플러그인 적용 및 공통 네비게이션/디자인 시스템 의존성 추가
*   `AndroidFeatureApiConventionPlugin`: Navigation3 런타임 및 Serialization 플러그인 추가
*   `core:navigation`: Compose 라이브러리 플러그인 적용 및 불필요한 의존성 제거
*   `AndroidCompose`: Compose 관련 테스트 의존성(`runtimeTesting`)을 컨벤션 내부로 이동
*   기타 각 모듈별로 산재해 있던 의존성(Navigation3, Immutable collections 등) 정리 및 추가
네비게이션 관련 로직을 정리하고 `PrezelAppState`의 역할을 강화했습니다.

*   **네비게이션 구조 개선**:
    *   `TOP_LEVEL_NAV_ITEMS`를 `persistentMapOf`로 변경하고 `ImmutableSet`을 적용하여 안정성을 높였습니다.
    *   `currentTopLevelKeyOrStart` 확장 함수를 제거하고 `PrezelAppState` 내 프로퍼티로 로직을 이동했습니다.
    *   `Navigator` 생성을 `PrezelApp` 수준으로 이동하여 `PrezelAppState`를 단순화했습니다.
*   **AppState 강화**: `shouldShowNavigationBar`, `currentTopLevelKey` 등 UI 상태 결정 로직을 `PrezelAppState` 내부로 캡슐화했습니다.
*   **디자인 시스템**: `PrezelCheckbox`의 활성화 상태 아이콘 색상을 `interactiveRegular`에서 `feedbackGoodRegular`로 변경했습니다.
*   **기타**: `NavigationState`에서 불필요한 리스트 정렬 로직을 제거했습니다.
history 피처를 api와 impl 모듈로 분리하여 새롭게 추가했습니다.

*   `settings.gradle.kts`에 `:feature:history:api`, `:feature:history:impl` 모듈 등록
*   각 모듈의 `build.gradle.kts`, `AndroidManifest.xml`, ProGuard 설정 등 기본 구조 생성
*   `feature:history:impl`에서 `feature:history:api` 의존성 추가
*   `feature:profile:api` 모듈의 `AndroidManifest.xml` 추가 (빈 파일)
profile 기능을 위한 api 및 impl 모듈을 생성하고 프로젝트에 등록했습니다.

* profile api 및 impl 모듈 구조 생성
* 각 모듈별 build.gradle.kts 및 기본 설정 파일 추가
* settings.gradle.kts에 신규 모듈 등록
*   History 및 Profile 피처의 API/Impl 모듈 기본 구조(Screen, ViewModel, NavKey)를 추가했습니다.
*   `HomeEntryProvider`를 제거하고, Hilt Multi-bindings(`IntoSet`)를 활용한 `EntryBuilder` 방식을 도입하여 각 피처가 스스로 네비게이션 엔트리를 등록하도록 개선했습니다.
*   앱의 하단 탭 메뉴(`TOP_LEVEL_NAV_ITEMS`)에 History와 Profile을 활성화했습니다.
*   `MainActivity` 및 `PrezelApp`에서 주입된 `entryBuilders`를 사용하도록 수정했습니다.
- `TopLevelNavItem`의 히스토리 및 프로필 아이콘, 타이틀 리소스를 각 피처에 맞게 수정했습니다.
- 네비게이션 전환 시 발생하는 애니메이션(`transitionSpec`)을 비활성화했습니다.
- `PrezelIcons`에 `Storage` 아이콘을 추가했습니다.
- 피처별(home, history, profile) API 문자열 리소스를 추가 및 정리했습니다.
- `Navigator` 및 `NavigationState` 내 주요 메서드와 클래스 주석을 한글로 번역하여 가독성을 높였습니다.
- `PrezelNavigationBar`의 배경색을 `PrezelTheme.colors.bgRegular`로 명시적으로 설정했습니다.
- 프리뷰 및 내부 코드에서 사용되던 `PrezelIcons.Blank`를 `PrezelIcons.Storage`로 교체했습니다.
- `PrezelNavigationBar` 상단에 1dp 두께의 구분선(borderRegular)을 추가했습니다.
- 선택되지 않은 네비게이션 아이템의 아이콘 색상을 `iconDisabled`에서 `iconRegular`로 변경했습니다.
@HamBeomJoon HamBeomJoon self-assigned this Feb 6, 2026
@HamBeomJoon HamBeomJoon added the ✨ feat 새로운 기능 추가 또는 기존 기능 확장 label Feb 6, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Walkthrough

이 PR은 앱 전역 네비게이션과 Scaffold 기반 UI 진입점을 추가합니다. Navigation3 기반의 NavigationState, Navigator, TopLevel NavKey들(Home/History/Profile)과 TOP_LEVEL_NAV_ITEMS를 도입하고, PrezelApp·PrezelAppState 및 CompositionLocal(LocalNavigator, LocalSnackbarHostState)을 추가해 네비게이션 호스트와 바텀 네비게이션을 구성합니다. MainActivity에 Hilt 진입점과 NetworkMonitor·entryBuilders 주입을 추가했으며, NetworkMonitor 인터페이스 및 ConnectivityManagerNetworkMonitor 구현과 해당 DI 바인딩을 포함합니다. 여러 feature 모듈과 관련 build 스크립트·리소스도 새로 추가되었습니다.

Possibly related PRs

  • PR 5: MainActivity를 생성/수정한 PR로 Hilt 진입점과 앱 진입점(PrezelApp) 와이어링에서 코드 중복 또는 연관성이 높음.
  • PR 12: 코어 네트워크·디스패처·DI 제공자(Dispatchers/CoroutineScope) 변경을 포함해 NetworkMonitor 및 DI 바인딩과 강한 코드 연결성이 있음.
  • PR 7: 빌드 로직·버전 카탈로그 및 플러그인 수정 관련 PR로 navigation3 및 관련 라이브러리 설정과 직접적인 빌드 수준 연관이 있음.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 Navigation3 기반 네비게이션 시스템 구축과 Scaffold 기본 구조 구현이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 제시된 템플릿의 필수 섹션들(작업 내용, 관련 이슈, 스크린샷)을 모두 포함하고 있으며, 상세한 구현 내용을 제공합니다.
Linked Issues check ✅ Passed Navigation3 의존성 추가, 네비게이션 시스템 구축, Scaffold 기반 UI 레이아웃 정의, BottomBar 및 SnackbarHost 구현 등 #48의 모든 주요 목표가 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Navigation3 시스템 구축 및 Scaffold 기본 구조 구현이라는 명확한 목표와 관련이 있습니다. 범위를 벗어난 변경사항은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelNavigationBar.kt (1)

3-3: import android.R가 앱 R 클래스와 충돌할 수 있습니다.

현재는 프리뷰에서만 android.R.string.*을 사용하지만, 이후 이 파일에서 앱의 R 클래스를 참조해야 할 경우 이름 충돌이 발생합니다. 프리뷰 내에서 정규화된 이름(android.R.string.untitled)을 사용하거나, 앱 리소스 문자열로 대체하는 것을 고려해 주세요.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@Prezel/app/build.gradle.kts`:
- Around line 30-33: The navigation3 dependencies
(implementation(libs.androidx.navigation3.runtime) and
implementation(libs.androidx.navigation3.ui)) should use the stable 1.0.0
release; open your version catalog (where libs.androidx.navigation3.runtime and
libs.androidx.navigation3.ui are defined), confirm their current versions, and
change them to "1.0.0" unless you explicitly need an alpha (e.g.,
1.1.0-alpha03). After updating the catalog entries, sync the Gradle project and
run a quick build to ensure no breaking changes; if any code depends on
alpha-only APIs, either keep the alpha with a comment explaining why or refactor
to work with 1.0.0.

In `@Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt`:
- Line 24: The Navigator instance is being remembered without a key so it won't
update when appState.navigationState changes; update the remember call that
creates Navigator to use appState.navigationState as its key (i.e., call
remember with appState.navigationState so Navigator(appState.navigationState) is
recreated on changes); target the remember usage that constructs Navigator and
ensure it observes appState.navigationState (related to rememberPrezelAppState)
so the Navigator no longer holds a stale navigationState reference.

In
`@Prezel/build-logic/convention/src/main/java/com/team/prezel/buildlogic/convention/plugin/AndroidFeatureApiConventionPlugin.kt`:
- Around line 15-17: The dependency on androidx.navigation3.runtime in
AndroidFeatureApiConventionPlugin.kt is declared with "implementation", but the
feature API module exposes NavKey subclasses (HistoryNavKey, HomeNavKey,
ProfileNavKey) that inherit androidx.navigation3.runtime.NavKey, so the
dependency must be public; change the dependency declaration that uses
libs.findLibrary("androidx.navigation3.runtime").get() from "implementation" to
"api" in the dependencies block so consumers can resolve the NavKey type.

In
`@Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelNavigationBar.kt`:
- Around line 69-91: The Icon's explicit tint in the Icon composable should be
removed so icon coloring is delegated to NavigationBarItemDefaults.colors; open
the Icon call (painterResource(iconRes), contentDescription = null, tint = ...)
in PrezelNavigationBar and delete the tint argument, and ensure
NavigationBarItemDefaults.colors(...) keeps the desired selectedIconColor and
unselectedIconColor values (adjust unselectedIconColor to
PrezelTheme.colors.iconRegular if you want the previous unselected look) so all
icon color decisions come from NavigationBarItemDefaults.colors rather than an
overriding tint.

In
`@Prezel/core/network/src/main/java/com/team/prezel/core/network/PrezelDisptachers.kt`:
- Line 1: 파일명이 잘못되어 있습니다: rename PrezelDisptachers.kt → PrezelDispatchers.kt; 실제
수정 작업은 파일을 git mv로 이동하여 히스토리를 유지하고 파일명 변경에 따라 프로젝트 전체의 모든 참조(imports,
Gradle/Kotlin build scripts, IDE run configurations, 및 다른 소스 파일에서 사용되는
클래스/objects)에서 "PrezelDisptachers"를 "PrezelDispatchers"로 업데이트하세요; 또한 파일 내
package 선언(com.team.prezel.core.network)은 그대로 두고 클래스/객체/파일레벨 심볼명이 파일명에 의존한다면 해당
심볼 이름도 일치시켜 주세요 (예: 파일에 있는 클래스/또는 object 이름이 PrezelDisptachers이면 이를
PrezelDispatchers로 변경).
🧹 Nitpick comments (12)
Prezel/build-logic/convention/src/main/java/com/team/prezel/buildlogic/convention/internal/AndroidCompose.kt (1)

21-21: 모든 Compose 모듈에 lifecycle.runtimeTesting 테스트 의존성이 추가됩니다.

이 컨벤션 플러그인은 모든 Compose 모듈에 적용되므로, androidx.lifecycle.runtimeTesting이 실제로 필요하지 않은 모듈에도 androidTest 의존성으로 포함됩니다. 특정 모듈(예: app 또는 feature impl)에서만 필요한 경우, 해당 모듈의 build.gradle.kts에 직접 선언하는 것이 더 적절할 수 있습니다.

Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/HistoryUiState.kt (1)

3-7: Success 상태가 누락되어 있습니다.

현재 LoadingLoadFailed만 정의되어 있으며, 데이터를 성공적으로 로드한 경우를 나타내는 Success 상태가 없습니다. 초기 scaffold 구현으로 의도된 것이라면 괜찮지만, 실제 데이터를 표시할 때 추가가 필요합니다. HomeUiState, ProfileUiState에도 동일하게 적용됩니다.

💡 향후 추가 예시
 sealed interface HistoryUiState {
     data object Loading : HistoryUiState
 
     data object LoadFailed : HistoryUiState
+
+    data class Success(val data: HistoryData) : HistoryUiState
 }
Prezel/feature/home/impl/src/main/java/com/team/prezel/feature/home/impl/HomeUiState.kt (1)

3-7: Success 상태가 누락되어 있습니다.

현재 LoadingLoadFailed만 정의되어 있어 정상 데이터 로드 완료 시 사용할 상태가 없습니다. 초기 스캐폴딩 단계라면 괜찮지만, 추후 Success (또는 데이터를 담는) 상태 추가가 필요합니다.

Prezel/feature/profile/impl/src/main/java/com/team/prezel/feature/profile/impl/ProfileUiState.kt (1)

3-7: HomeUiState와 동일한 구조 — Success 상태 추가 고려

HomeUiState와 완전히 동일한 구조입니다. 마찬가지로 Success 상태가 누락되어 있으므로 추후 추가가 필요합니다. 여러 feature에서 Loading/LoadFailed 패턴이 반복된다면, core 모듈에 공통 base interface를 두는 것도 고려해 볼 수 있습니다.

Prezel/feature/history/impl/src/main/java/com/team/prezel/feature/history/impl/navigation/HistoryEntryBuilder.kt (1)

13-17: featureHistoryEntryBuilder()의 가시성을 internal로 제한하는 것을 고려해 주세요.

이 확장 함수는 동일 모듈 내 FeatureHistoryModule에서만 호출되므로, internal 키워드를 추가하면 모듈 외부로의 불필요한 노출을 방지할 수 있습니다. HomeEntryBuilder, ProfileEntryBuilder에도 동일하게 적용됩니다.

♻️ 제안
-fun EntryProviderScope<NavKey>.featureHistoryEntryBuilder() {
+internal fun EntryProviderScope<NavKey>.featureHistoryEntryBuilder() {
Prezel/app/src/main/java/com/team/prezel/navigation/TopLevelNavItem.kt (1)

21-34: HOME, HISTORY, PROFILE — top-level 변수 스코핑 개선을 고려해 주세요.

HOME, HISTORY, PROFILE은 매우 범용적인 이름의 top-level val입니다. 다른 모듈에서 import 시 이름 충돌이나 혼동이 발생할 수 있으므로, private으로 선언하거나 TopLevelNavItem의 companion object 내에 배치하는 것을 권장합니다. 외부에서는 TOP_LEVEL_NAV_ITEMS 맵을 통해 접근하면 충분합니다.

♻️ private으로 제한하는 예시
-val HOME = TopLevelNavItem(
+private val HOME = TopLevelNavItem(
     iconRes = PrezelIcons.Home,
     titleTextId = homeR.string.feature_home_api_title,
 )

-val HISTORY = TopLevelNavItem(
+private val HISTORY = TopLevelNavItem(
     iconRes = PrezelIcons.Storage,
     titleTextId = historyR.string.feature_history_api_title,
 )

-val PROFILE = TopLevelNavItem(
+private val PROFILE = TopLevelNavItem(
     iconRes = PrezelIcons.Profile,
     titleTextId = profileR.string.feature_profile_api_title,
 )
Prezel/core/data/src/main/java/com/team/prezel/core/data/di/DataModule.kt (1)

13-14: @Singleton 스코프 누락 고려

@Binds 메서드에 스코프 어노테이션이 없으므로, NetworkMonitor가 주입될 때마다 새로운 ConnectivityManagerNetworkMonitor 인스턴스가 생성됩니다. 각 인스턴스마다 별도의 NetworkCallback이 등록되므로, @Singleton을 추가하여 인스턴스를 재사용하는 것이 좋습니다.

♻️ 제안된 수정
+import javax.inject.Singleton
+
 `@Module`
 `@InstallIn`(SingletonComponent::class)
 abstract class DataModule {
+    `@Singleton`
     `@Binds`
     internal abstract fun bindsNetworkMonitor(networkMonitor: ConnectivityManagerNetworkMonitor): NetworkMonitor
 }
Prezel/core/navigation/src/main/java/com/team/prezel/core/navigation/NavigationState.kt (3)

67-84: toEntries()가 리컴포지션마다 새로운 SnapshotStateList를 생성합니다

toMutableStateList()는 매번 새 리스트 인스턴스를 반환합니다. NavHost가 참조 동등성(reference equality) 대신 키 기반으로 비교한다면 문제가 없겠지만, 불필요한 할당과 잠재적 리컴포지션을 유발할 수 있습니다. remember + derivedStateOf 등을 활용하여 리스트를 캐싱하는 것을 고려해 보세요.


69-80: mapValues 내에서 @Composable 함수 호출의 안정성 확인

rememberSaveableStateHolderNavEntryDecoratorrememberViewModelStoreNavEntryDecoratorsubStacks.mapValues 루프 안에서 호출됩니다. subStacks가 불변이므로 반복 횟수가 일정하여 현재는 안전하지만, 향후 동적 탭 추가/제거가 발생하면 Compose의 호출 순서 제약(positional memoization)을 위반할 수 있습니다.

이 점을 염두에 두고, 동적 변경이 필요해지면 key(navKey) 블록으로 감싸는 리팩토링을 고려해 주세요.


45-62: topLevelStack 또는 currentSubStack이 비어 있을 경우 크래시 발생 가능

currentTopLevelKey(Line 51)와 currentKey(Line 61) 모두 .last()를 사용하여 빈 리스트에서 NoSuchElementException을 던질 수 있습니다. rememberNavBackStack이 초기 키를 포함하므로 정상 흐름에서는 비어 있지 않겠지만, 방어적으로 lastOrNull() 사용 혹은 비어 있을 때의 폴백 처리를 고려해 보세요.

Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelNavigationBar.kt (1)

183-185: "Nia" 접두사는 "Now in Android" 샘플 앱에서 온 것으로 보입니다. PrezelNavigationBarPreview로 이름을 변경하고 private으로 설정해 주세요.

♻️ 수정 제안
 `@ThemePreview`
 `@Composable`
-fun NiaNavigationBarPreview() {
+private fun PrezelNavigationBarPreview() {
Prezel/core/network/src/main/java/com/team/prezel/core/network/di/CoroutineScopeModule.kt (1)

15-17: ApplicationScope 한정자를 별도 파일로 분리하는 것을 고려해 주세요.

현재 DI 모듈 파일 내에 한정자 어노테이션이 정의되어 있어, 다른 모듈에서 이 한정자를 참조할 때 발견하기 어려울 수 있습니다. PrezelDispatchers.kt처럼 별도 파일로 분리하면 일관성이 향상됩니다.

프로젝트 내 여러 모듈의 `.gitignore` 및 `proguard-rules.pro` 파일의 개행 및 형식을 정리했습니다.
*   `libs.versions.toml`: `androidxActivity` 버전 변수를 통합하고 `navigation3` 관련 변수를 단일화했습니다.
*   `AndroidFeatureApiConventionPlugin`: `androidx.navigation3.runtime` 의존성을 `implementation`에서 `api`로 변경했습니다.
*   `PrezelNavigationBar`: `Icon`의 개별 `tint` 설정을 제거하고 `NavigationBarItemDefaults.colors`를 통해 색상을 관리하도록 수정했습니다.
*   `core:network`: 오타가 있던 파일명을 `PrezelDisptachers.kt`에서 `PrezelDispatchers.kt`로 변경했습니다.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@Prezel/gradle/libs.versions.toml`:
- Around line 77-80: Remove the unused dependency entries keyed as
"androidx-appcompat" and "material" from libs.versions.toml (these correspond to
androidx.appcompat:appcompat and com.google.android.material:material); delete
their lines/entries so they are no longer referenced by the build, but leave the
"androidx-navigation3-runtime" and "androidx-navigation3-ui" entries intact
since those are used by NavigationState.kt, PrezelApp.kt, and MainActivity.kt.
- Line 102: Remove the unnecessary android-library plugin declaration from
libs.versions.toml (the line defining android-library = { id =
"com.android.library", version.ref = "agp" }) because it’s not referenced
directly and all library modules use the convention plugin
prezel-android-library which applies com.android.library internally; delete that
entry and verify there are no remaining references to the symbol android-library
(and that Prezel/build.gradle.kts can drop any corresponding apply false) so the
version catalog contains only actually-used plugin entries.
🧹 Nitpick comments (1)
Prezel/gradle/libs.versions.toml (1)

79-80: Navigation3 라이브러리의 implementation vs api 사용에 주의하세요.

PR 커밋 메시지에서 일부 navigation3 의존성을 implementation에서 api로 변경했다고 언급되어 있습니다. navigation3-runtimenavigation3-uiapi로 노출할 경우, 이를 의존하는 모든 모듈에서 Navigation3 타입이 전이적으로 노출됩니다. 의도적으로 NavKey 등의 타입을 다른 모듈에서 참조해야 하는 경우에만 api를 사용하고, 그렇지 않으면 implementation으로 제한하는 것이 좋습니다.

* `PrezelApp`의 내비게이션 전환 효과에 100ms fade-in/out 애니메이션을 적용했습니다.
* `libs.versions.toml`에서 사용하지 않는 `appcompat` 및 `material` 의존성을 제거했습니다.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Prezel/gradle/libs.versions.toml`:
- Around line 11-13: The libs.versions.toml adds androidxSavedStateCompose =
"1.4.0" and an androidx-savedstate-compose library entry but the codebase
doesn't reference it; either remove the androidxSavedStateCompose version and
the corresponding androidx-savedstate-compose library entry from
libs.versions.toml (and any unused alias in build files) or update code to
actually use the savedstate-compose artifact where intended (e.g., replace
usages that need saved-state support with the androidx-savedstate-compose
alias); ensure you edit the symbols androidxSavedStateCompose and the
androidx-savedstate-compose alias consistently across the toml and
build.gradle(.kts) files.
🧹 Nitpick comments (1)
Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt (1)

26-28: entryProvider가 매 리컴포지션마다 재생성됩니다.

entryProvider { ... }@Composable이 아닌 함수를 반환하는데, remember 없이 호출되면 매 리컴포지션마다 새 인스턴스가 생성됩니다. Hilt를 통해 주입되는 안정적인 entryBuilders Set을 기준으로 메모이제이션하면 불필요한 재생성을 방지할 수 있습니다. 이는 Android Navigation3 공식 가이드의 권장 패턴입니다.

♻️ 수정 제안
-    val entryProvider = entryProvider {
-        entryBuilders.forEach { builder -> this.builder() }
-    }
+    val entryProvider = remember(entryBuilders) {
+        entryProvider {
+            entryBuilders.forEach { builder -> this.builder() }
+        }
+    }

* `PrezelApp`의 내비게이션 전환 효과에 100ms fade-in/out 애니메이션을 적용했습니다.
* `libs.versions.toml`에서 사용하지 않는 `appcompat` 및 `material` 의존성을 제거했습니다.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Prezel/gradle/libs.versions.toml`:
- Line 24: The [versions] key "material" is declared but no library in the
catalog references it; remove the unused version key material = "1.13.0" from
the libs.versions.toml [versions] section and, before committing, search the
project for any remaining references to the material version key (if you find
references either restore the corresponding library entry or update those
references to the correct version key).
🧹 Nitpick comments (2)
Prezel/app/src/main/java/com/team/prezel/navigation/TopLevelNavItem.kt (2)

14-17: @param:DrawableRes 대신 @get:DrawableRes 사용 고려

@param:DrawableRes는 생성자 파라미터에만 적용되므로, 프로퍼티 접근 시(item.iconRes) Android Lint 검사가 동작하지 않습니다. @get:DrawableRes를 사용하면 getter에도 어노테이션이 적용되어 Lint가 올바르게 동작합니다.

제안
 data class TopLevelNavItem(
-    `@param`:DrawableRes val iconRes: Int,
-    `@param`:StringRes val titleTextId: Int,
+    `@DrawableRes` val iconRes: Int,
+    `@StringRes` val titleTextId: Int,
 )

19-32: HOME, HISTORY, PROFILE 상수의 가시성을 private으로 제한 권장

이 상수들은 TOP_LEVEL_NAV_ITEMS 맵 구성에만 사용되며, 매우 일반적인 이름이라 다른 파일에서 import 시 네이밍 충돌 가능성이 있습니다. private으로 선언하면 불필요한 외부 노출을 방지할 수 있습니다.

제안
-val HOME = TopLevelNavItem(
+private val HOME = TopLevelNavItem(
     iconRes = PrezelIcons.Home,
     titleTextId = R.string.bottom_nav_home,
 )

-val HISTORY = TopLevelNavItem(
+private val HISTORY = TopLevelNavItem(
     iconRes = PrezelIcons.Storage,
     titleTextId = R.string.bottom_nav_history,
 )

-val PROFILE = TopLevelNavItem(
+private val PROFILE = TopLevelNavItem(
     iconRes = PrezelIcons.Profile,
     titleTextId = R.string.bottom_nav_profile,
 )

- `AndroidFeatureApiConventionPlugin`에서 `androidx.navigation3.runtime` 의존성을 `api`에서 `implementation`으로 변경했습니다.
- `app/build.gradle.kts`에서 중복된 `androidx.navigation3.runtime` 의존성을 제거하고 코드를 정리했습니다.
* `PrezelAppState`의 불필요한 KDoc 주석 삭제
* `libs.versions.toml`에서 미사용 `material` 의존성 제거
`libs.versions.toml`과 `build.gradle.kts`에서 더 이상 사용되지 않는 `com.android.library` 플러그인 종속성을 제거합니다.
- `LocalNavigator`, `LocalSnackbarHostState` staticCompositionLocal 추가
- `PrezelApp`에서 `CompositionLocalProvider`를 사용하여 하위 컴포넌트에 navigator와 snackbarHostState 주입
- `PrezelNavigationScaffold`에 `SnackbarHost` 추가 및 `snackbarHostState` 파라미터 요구하도록 변경
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt`:
- Around line 32-75: PrezelApp is too long; extract the heavy lambdas into
private composables: move the TOP_LEVEL_NAV_ITEMS iteration into a new
`@Composable` function (e.g., PrezelNavigationItems(navigator: Navigator,
currentTopLevelKey: NavKey, onNavigate: (NavKey)->Unit)) and move the NavDisplay
invocation and its transitionSpec/popTransitionSpec into another private
`@Composable` (e.g., PrezelNavDisplay(entries, onBack, modifier)); then replace
the navigationItems lambda passed to PrezelNavigationScaffold with a call to
PrezelNavigationItems and call PrezelNavDisplay inside the scaffold content
lambda, keeping the same parameters (snackbarHostState, navigator,
appState.navigationState.toEntries(entryProvider),
transitionSpec/popTransitionSpec) and reusing existing symbols PrezelApp,
PrezelNavigationScaffold, TOP_LEVEL_NAV_ITEMS, NavDisplay and Navigator.

In
`@Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelNavigationBar.kt`:
- Around line 72-76: 네비게이션 아이콘의 contentDescription이 null로 설정되어 있어 접근성 문제가 있으므로,
PrezelNavigationBar의 icon lambda에서 contentDescription을 null 대신 해당 탭의 레이블을 제공하도록
변경하세요; 구체적으로 Icon(painter = painterResource(iconRes), contentDescription =
stringResource(labelTextId))처럼 labelTextId를 stringResource로 사용하여 스크린리더가 읽을 수 있게
만드세요.
🧹 Nitpick comments (1)
Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/PrezelNavigationBar.kt (1)

97-103: 필수 파라미터 snackbarHostState가 선택적 파라미터 뒤에 위치합니다.

Compose API 컨벤션에 따르면 기본값이 없는 필수 파라미터가 기본값이 있는 선택적 파라미터(showNavigationBar) 앞에 와야 합니다. 호출부에서 named argument 없이 사용할 때 혼란을 줄 수 있습니다.

♻️ 파라미터 순서 조정 제안
 `@Composable`
 fun PrezelNavigationScaffold(
     navigationItems: `@Composable` PrezelNavigationScope.() -> Unit,
+    snackbarHostState: SnackbarHostState,
     modifier: Modifier = Modifier,
     showNavigationBar: Boolean = true,
-    snackbarHostState: SnackbarHostState,
     content: `@Composable` (PaddingValues) -> Unit,
 )

- `PrezelApp`의 UI 렌더링 부분을 `PrezelAppRoot` 컴포포저블로 분리했습니다.
- `Navigator` 생성 시 `appState.navigationState`를 key로 사용하여 불필요한 재생성을 방지하도록 수정했습니다.
- `entryProvider`를 별도 변수로 추출하여 가독성을 개선했습니다.
`PrezelApp`의 내부 구조를 정리하고 컴포저블 분리를 통해 가독성을 개선했습니다.

*   `PrezelAppRoot`를 `PrezelAppContent`로 이름을 변경하고 내부 로직을 이동했습니다.
*   `entryProvider` 생성 시 `remember`를 사용하여 불필요한 재계산을 방지하도록 수정했습니다.
*   `CompositionLocalProvider` 내에서 `LocalNavigator`와 `LocalSnackbarHostState`를 주입하고 하위 컴포저블에서 사용하도록 구조를 변경했습니다.
*   불필요한 import문을 제거했습니다.
비어있던 `contentDescription`에 `labelTextId`를 활용한 `stringResource`를 할당하여 접근성을 개선했습니다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 새로운 기능 추가 또는 기존 기능 확장

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Navigation3 기반 앱 네비게이션 시스템 구축 및 Scaffold 기본 구조 구현

2 participants