Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
94a0147
feat: core:navigation 모듈 추가 및 Navigation3 기반 구조 구현
HamBeomJoon Feb 1, 2026
03ceb88
feat: home feature api 모듈 추가
HamBeomJoon Feb 1, 2026
4634c6b
feat: home feature 구현 모듈 추가
HamBeomJoon Feb 1, 2026
761de3f
feat: 최상위 내비게이션 아이템 정의 및 의존성 추가
HamBeomJoon Feb 1, 2026
155e80a
feat: Coroutine Dispatcher 및 Scope 관련 Hilt 모듈 추가
HamBeomJoon Feb 1, 2026
8a61a35
feat: 네트워크 상태 모니터링 기능 추가
HamBeomJoon Feb 1, 2026
ea548a2
feat: Navigation3 기반의 기본 앱 구조 및 홈 화면 연동
HamBeomJoon Feb 1, 2026
b584d70
feat: BottomNavigationBar 컴포넌트 추가
HamBeomJoon Feb 1, 2026
d1890c9
feat: PrezelNavigationScaffold 및 NavigationScope 추가
HamBeomJoon Feb 1, 2026
eaeddd8
feat: 하위 탐색 구조 및 Navigation Bar 적용
HamBeomJoon Feb 1, 2026
82e7ca9
Merge remote-tracking branch 'origin/develop' into feat/#48-prezel-ap…
HamBeomJoon Feb 5, 2026
c5daeb7
build: 컨벤션 플러그인 및 모듈 의존성 구조 개선
HamBeomJoon Feb 6, 2026
6db1a4d
refactor: 네비게이션 상태 관리 로직 개선 및 코드 정리
HamBeomJoon Feb 6, 2026
5131676
feat: history 피처 모듈 생성
HamBeomJoon Feb 6, 2026
b9ac2ac
feat: profile 모듈 추가
HamBeomJoon Feb 6, 2026
8f26acf
feat: History, Profile 피처 추가 및 네비게이션 등록 방식 개선
HamBeomJoon Feb 6, 2026
6a000be
feat: 네비게이션 아이템 정보 수정 및 전환 애니메이션 제거
HamBeomJoon Feb 6, 2026
5754714
docs: Navigator 및 NavigationState 주석 한글화 및 UI 개선
HamBeomJoon Feb 6, 2026
862a0c2
feat: PrezelNavigationBar 상단 구분선 추가 및 아이콘 색상 수정
HamBeomJoon Feb 6, 2026
7adcabf
style: 개행 및 불필요한 변경 사항 정리
HamBeomJoon Feb 6, 2026
1fd6f5f
build: 라이브러리 버전 관리 방식 및 의존성 설정 변경
HamBeomJoon Feb 6, 2026
8dfcf6f
feat: 화면 전환 애니메이션 추가 및 미사용 의존성 제거
HamBeomJoon Feb 6, 2026
9a01872
feat: 화면 전환 애니메이션 추가 및 미사용 의존성 제거
HamBeomJoon Feb 7, 2026
f5000ff
refactor: navigation3 의존성 구성 변경
HamBeomJoon Feb 7, 2026
c1cf1cb
chore: 불필요한 주석 및 의존성 제거
HamBeomJoon Feb 7, 2026
f099ab3
chore: 불필요한 android-library 플러그인 제거
HamBeomJoon Feb 7, 2026
e539419
feat: CompositionLocal을 통한 Navigator 및 SnackbarHostState 전역 관리 설정
HamBeomJoon Feb 8, 2026
56e55bd
refactor: PrezelApp 구조 분리 및 Navigator 초기화 로직 수정
HamBeomJoon Feb 8, 2026
8503429
refactor: PrezelApp 구조 개선 및 리팩터링
HamBeomJoon Feb 8, 2026
7e5f5a2
feat: PrezelNavigationBar 아이콘의 contentDescription 추가
HamBeomJoon Feb 8, 2026
c84fee3
chore: androidx-activity 버전 업데이트 및 activity-ktx 의존성 제거
HamBeomJoon Feb 13, 2026
7b27f46
refactor: 불필요한 AndroidManifest.xml 파일 삭제 및 정리
HamBeomJoon Feb 13, 2026
e738cb2
refactor: LocalNavigator 및 LocalSnackbarHostState 위치 이동
HamBeomJoon Feb 13, 2026
4f81cd6
feat: PrezelNavigationBar 아이템 파라미터 타입 변경 및 IconSource 도입
HamBeomJoon Feb 13, 2026
d8e05f3
refactor: PrezelNavigationBar preview 코드 구조 개선
HamBeomJoon Feb 13, 2026
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
11 changes: 10 additions & 1 deletion Prezel/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.prezel.android.application.compose)
alias(libs.plugins.prezel.hilt)
alias(libs.plugins.kotlinx.serialization)
}

android {
Expand All @@ -14,9 +15,17 @@ android {
dependencies {
implementation(projects.coreData)
implementation(projects.coreDesignsystem)
implementation(projects.coreNavigation)
implementation(projects.featureHomeApi)
implementation(projects.featureHomeImpl)
implementation(projects.featureHistoryApi)
implementation(projects.featureHistoryImpl)
implementation(projects.featureProfileApi)
implementation(projects.featureProfileImpl)

implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.navigation3.ui)
implementation(libs.timber)
implementation(libs.kotlinx.collections.immutable)
}
55 changes: 23 additions & 32 deletions Prezel/app/src/main/java/com/team/prezel/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,38 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.team.prezel.core.data.NetworkMonitor
import com.team.prezel.core.designsystem.theme.PrezelTheme
import com.team.prezel.ui.PrezelApp
import com.team.prezel.ui.rememberPrezelAppState
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var networkMonitor: NetworkMonitor

@Inject
lateinit var entryBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()

setContent {
PrezelTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding),
)
}
val appState = rememberPrezelAppState(
networkMonitor = networkMonitor,
)

PrezelApp(
appState = appState,
entryBuilders = entryBuilders,
)
}
}
}
}

@Composable
fun Greeting(
name: String,
modifier: Modifier = Modifier,
) {
Text(
text = "Hello $name!",
modifier = modifier,
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
PrezelTheme {
Greeting("Android")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.team.prezel.navigation

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.navigation3.runtime.NavKey
import com.team.prezel.R
import com.team.prezel.core.designsystem.icon.PrezelIcons
import com.team.prezel.feature.history.api.HistoryNavKey
import com.team.prezel.feature.home.api.HomeNavKey
import com.team.prezel.feature.profile.api.ProfileNavKey
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentMapOf

data class TopLevelNavItem(
@param:DrawableRes val iconRes: Int,
@param:StringRes val titleTextId: Int,
)

val HOME = TopLevelNavItem(
iconRes = PrezelIcons.Home,
titleTextId = R.string.bottom_nav_home,
)

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

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

val TOP_LEVEL_NAV_ITEMS = persistentMapOf(
HomeNavKey to HOME,
HistoryNavKey to HISTORY,
ProfileNavKey to PROFILE,
)

val TOP_LEVEL_KEYS: ImmutableSet<NavKey> = TOP_LEVEL_NAV_ITEMS.keys
87 changes: 87 additions & 0 deletions Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.team.prezel.ui

import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import com.team.prezel.core.designsystem.component.PrezelNavigationScaffold
import com.team.prezel.core.designsystem.icon.DrawableIcon
import com.team.prezel.core.navigation.LocalNavigator
import com.team.prezel.core.navigation.LocalSnackbarHostState
import com.team.prezel.core.navigation.Navigator
import com.team.prezel.core.navigation.toEntries
import com.team.prezel.navigation.TOP_LEVEL_NAV_ITEMS

@Composable
fun PrezelApp(
appState: PrezelAppState,
entryBuilders: Set<EntryProviderScope<NavKey>.() -> Unit>,
) {
val navigator = remember(appState.navigationState) { Navigator(appState.navigationState) }
val snackbarHostState = remember { SnackbarHostState() }

CompositionLocalProvider(
LocalNavigator provides navigator,
LocalSnackbarHostState provides snackbarHostState,
) {
PrezelAppContent(
appState = appState,
entryBuilders = entryBuilders,
)
}
}

@Composable
private fun PrezelAppContent(
appState: PrezelAppState,
entryBuilders: Set<EntryProviderScope<NavKey>.() -> Unit>,
) {
val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current

val provider = remember(entryBuilders) {
entryProvider {
entryBuilders.forEach { builder -> this.builder() }
}
}

PrezelNavigationScaffold(
showNavigationBar = appState.shouldShowNavigationBar,
snackbarHostState = snackbarHostState,
navigationItems = {
TOP_LEVEL_NAV_ITEMS.forEach { (key, item) ->
item(
selected = key == appState.navigationState.currentTopLevelKey,
onClick = { navigator.navigate(key) },
label = stringResource(item.titleTextId),
icon = DrawableIcon(item.iconRes),
)
}
},
) { padding ->
NavDisplay(
entries = appState.navigationState.toEntries(provider),
onBack = navigator::goBack,
modifier = Modifier.padding(padding),
transitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))
},
popTransitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))
},
)
}
}
59 changes: 59 additions & 0 deletions Prezel/app/src/main/java/com/team/prezel/ui/PrezelAppState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.team.prezel.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import com.team.prezel.core.data.NetworkMonitor
import com.team.prezel.core.navigation.NavigationState
import com.team.prezel.core.navigation.rememberNavigationState
import com.team.prezel.feature.home.api.HomeNavKey
import com.team.prezel.navigation.TOP_LEVEL_KEYS
import com.team.prezel.navigation.TOP_LEVEL_NAV_ITEMS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@Composable
fun rememberPrezelAppState(
networkMonitor: NetworkMonitor,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
): PrezelAppState {
val navigationState = rememberNavigationState(
startKey = HomeNavKey,
topLevelKeys = TOP_LEVEL_NAV_ITEMS.keys,
)

return remember(
navigationState,
coroutineScope,
networkMonitor,
) {
PrezelAppState(
navigationState = navigationState,
coroutineScope = coroutineScope,
networkMonitor = networkMonitor,
)
}
}

@Stable
class PrezelAppState(
val navigationState: NavigationState,
coroutineScope: CoroutineScope,
networkMonitor: NetworkMonitor,
) {
val shouldShowNavigationBar
get() = navigationState.currentKey in TOP_LEVEL_KEYS

val isOffline: StateFlow<Boolean> =
networkMonitor.isOnline
.map(Boolean::not)
.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false,
)
}
4 changes: 4 additions & 0 deletions Prezel/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<resources>
<string name="app_name">Prezel</string>

<string name="bottom_nav_home">홈</string>
<string name="bottom_nav_history">히스토리</string>
<string name="bottom_nav_profile">프로필</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal fun Project.configureAndroidCompose(commonExtension: CommonExtension<*,
"implementation"(platform(bom))
"implementation"(libs.findBundle("android-compose").get())
"androidTestImplementation"(platform(bom))
"androidTestImplementation"(libs.findLibrary("androidx.lifecycle.runtimeTesting").get())
Copy link
Member

Choose a reason for hiding this comment

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

이거 뭐 때문에 필요한건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

collectAsStateWithLifecycle, repeatOnLifecycle 같은 “Lifecycle 기반 수집”을 검증할 때 등등 테스트시에 필요하다고 하는데, 빼고 나중에 필요할때 추가하는게 나을까요?

"implementation"(libs.findLibrary("androidx-compose-ui-tooling-preview").get())
"debugImplementation"(libs.findLibrary("androidx-compose-ui-tooling").get())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.team.prezel.buildlogic.convention.plugin

import com.team.prezel.buildlogic.convention.internal.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
Expand All @@ -12,7 +13,7 @@ class AndroidFeatureApiConventionPlugin : Plugin<Project> {
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")

dependencies {
// "api"(project(":core:navigation"))
"implementation"(libs.findLibrary("androidx.navigation3.runtime").get())
}
Comment on lines 15 to 17
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find NavKey related files in the codebase
echo "=== Searching for NavKey definitions ==="
fd -i navkey -e kt -e java --type f

echo ""
echo "=== Searching for NavKey class/interface definitions ==="
rg -n "class NavKey|interface NavKey" --type=kt --type=java

echo ""
echo "=== Check feature API modules structure ==="
fd -type d "feature" | head -20

Repository: Team-Prezel/Prezel-Android

Length of output: 701


🏁 Script executed:

echo "=== HistoryNavKey.kt content ==="
cat Prezel/feature/history/api/src/main/java/com/team/prezel/feature/history/api/HistoryNavKey.kt

echo ""
echo "=== HomeNavKey.kt content ==="
cat Prezel/feature/home/api/src/main/java/com/team/prezel/feature/home/api/HomeNavKey.kt

echo ""
echo "=== ProfileNavKey.kt content ==="
cat Prezel/feature/profile/api/src/main/java/com/team/prezel/feature/profile/api/ProfileNavKey.kt

echo ""
echo "=== Check all imports from androidx.navigation in feature API modules ==="
rg "import androidx.navigation" Prezel/feature/*/api/src --type-list | head -20

Repository: Team-Prezel/Prezel-Android

Length of output: 1183


🏁 Script executed:

echo "=== Check what modules depend on feature API modules ==="
rg "implementation.*feature.*api|api.*feature.*api" Prezel --type gradle -A 2 -B 2 | head -30

echo ""
echo "=== Check gradle files in feature modules ==="
fd "build.gradle" Prezel/feature -A 5 | head -40

Repository: Team-Prezel/Prezel-Android

Length of output: 421


Feature API 모듈에서 implementation 대신 api를 사용해야 합니다.

Feature API 모듈의 NavKey 클래스들(HistoryNavKey, HomeNavKey, ProfileNavKey)이 androidx.navigation3.runtimeNavKey를 직접 상속하고 있습니다. 이는 NavKey가 public API 표면의 일부가 되므로, 이를 의존하는 모듈에서도 접근 가능해야 합니다. implementation 범위는 이 의존성을 비공개로 만들기 때문에, 다른 모듈에서 이 API를 사용할 때 NavKey 타입을 resolve할 수 없어 컴파일 에러가 발생합니다.

🔧 api로 변경 제안
             dependencies {
-                "implementation"(libs.findLibrary("androidx.navigation3.runtime").get())
+                "api"(libs.findLibrary("androidx.navigation3.runtime").get())
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dependencies {
// "api"(project(":core:navigation"))
"implementation"(libs.findLibrary("androidx.navigation3.runtime").get())
}
dependencies {
"api"(libs.findLibrary("androidx.navigation3.runtime").get())
}
🤖 Prompt for AI Agents
In
`@Prezel/build-logic/convention/src/main/java/com/team/prezel/buildlogic/convention/plugin/AndroidFeatureApiConventionPlugin.kt`
around lines 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.

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.gradle.kotlin.dsl.dependencies
class AndroidFeatureImplConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
apply(plugin = "prezel.android.library")
apply(plugin = "prezel.android.library.compose")
apply(plugin = "prezel.hilt")

extensions.configure<LibraryExtension> {
Expand All @@ -20,14 +20,9 @@ class AndroidFeatureImplConventionPlugin : Plugin<Project> {

dependencies {
// "implementation"(project(":core:ui"))
"implementation"(project(":core:designsystem"))

"implementation"(libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
"implementation"(libs.findLibrary("androidx.lifecycle.viewModelCompose").get())

"androidTestImplementation"(
libs.findLibrary("androidx.lifecycle.runtimeTesting").get(),
)
"implementation"(project(":core-designsystem"))
"implementation"(project(":core-navigation"))
"implementation"(libs.findLibrary("androidx.navigation3.ui").get())
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Prezel/core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ android {

dependencies {
implementation(projects.coreNetwork)

implementation(libs.kotlinx.coroutines.core)
}
3 changes: 2 additions & 1 deletion Prezel/core/data/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
Loading