Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# CLAUDE.md

## Code Style Rules

### No Inline Imports (FQCN 직접 사용 금지)
- 클래스를 사용할 때 반드시 상단에 `import` 문을 선언하고, 코드 본문에서는 단순 클래스명만 사용할 것
- `android.app.Activity`처럼 FQCN(Fully Qualified Class Name)을 코드 본문에 직접 작성하지 않을 것
- Bad: `(context as? android.app.Activity)?.finish()`
- Good: `import android.app.Activity` 후 `(context as? Activity)?.finish()`
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
implementation(projects.core.firebaseCrashlytics)
implementation(projects.core.firebaseMessaging)
implementation(projects.core.composeLocals)
implementation(projects.core.designsystem)
implementation(projects.core.work)
implementation(projects.data.domain)
implementation(projects.data.club)
Expand All @@ -90,6 +91,9 @@ dependencies {
implementation(projects.data.notification)
implementation(projects.data.place)
implementation(projects.domain.navigation)
implementation(projects.core.navigation)
implementation(libs.navigation3.runtime)
implementation(libs.navigation3.ui)
implementation(projects.feature.editDepartments)
implementation(projects.feature.editSubscription)
implementation(projects.feature.feedback)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
android:usesCleartextTraffic="true"
tools:replace="icon, label">

<activity
android:name=".HostActivity"
android:exported="false" />

<activity
android:name=".splash.SplashActivity"
android:exported="true"
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/com/ku_stacks/ku_ring/HostActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.ku_stacks.ku_ring

import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import com.ku_stacks.ku_ring.compose.locals.KuringCompositionLocalProvider
import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme
import com.ku_stacks.ku_ring.navigation.EntryBuilderProvider
import com.ku_stacks.ku_ring.navigation.keys.SplashKey
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class HostActivity : ComponentActivity() {

@Inject
lateinit var entryBuilders: @JvmSuppressWildcards Set<EntryBuilderProvider>

override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KuringCompositionLocalProvider {
KuringTheme {
val backStack = rememberNavBackStack(SplashKey)
NavDisplay(
backStack = backStack,
onBack = { if (backStack.removeLastOrNull() == null) finish() },
entryProvider = entryProvider {
entryBuilders.forEach { builder ->
with(builder) { provide() }
}
}
)
}
}
}
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/ku_stacks/ku_ring/di/NavigationModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ku_stacks.ku_ring.di

import com.ku_stacks.ku_ring.navigation.EntryBuilderProvider
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.Multibinds

@Module
@InstallIn(SingletonComponent::class)
abstract class NavigationModule {
@Multibinds
abstract fun bindEntryBuilderProviders(): Set<EntryBuilderProvider>
}
4 changes: 4 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ gradlePlugin {
id = "com.ku_stacks.ku_ring.buildlogic.primitive.common-android"
implementationClass = "com.ku_stacks.ku_ring.buildlogic.primitive.CommonAndroidPlugin"
}
register("com.ku_stacks.ku_ring.buildlogic.primitive.Navigation3Plugin") {
id = "com.ku_stacks.ku_ring.buildlogic.primitive.navigation3"
implementationClass = "com.ku_stacks.ku_ring.buildlogic.primitive.Navigation3Plugin"
}
register("com.ku_stacks.ku_ring.buildlogic.primitive.AndroidTestPlugin") {
id = "com.ku_stacks.ku_ring.buildlogic.primitive.android-test"
implementationClass = "com.ku_stacks.ku_ring.buildlogic.primitive.AndroidTestPlugin"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ku_stacks.ku_ring.buildlogic.primitive

import com.ku_stacks.ku_ring.buildlogic.dsl.implementation
import com.ku_stacks.ku_ring.buildlogic.dsl.library
import com.ku_stacks.ku_ring.buildlogic.dsl.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies

class Navigation3Plugin : Plugin<Project> {
override fun apply(target: Project) =
with(target) {
dependencies {
implementation(libs.library("navigation3-runtime"))
implementation(libs.library("navigation3-ui"))
implementation(libs.library("lifecycle-viewmodel-navigation3"))
}
}
}
1 change: 1 addition & 0 deletions core/navigation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
13 changes: 13 additions & 0 deletions core/navigation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import com.ku_stacks.ku_ring.buildlogic.dsl.setNameSpace

plugins {
kuring("feature")
}

android {
setNameSpace("core.navigation")
}

dependencies {
api(libs.navigation3.runtime)
}
3 changes: 3 additions & 0 deletions core/navigation/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Keep all @Serializable NavKey implementations for Navigation 3 back stack serialization
-keep @kotlinx.serialization.Serializable class * implements androidx.navigation3.runtime.NavKey { *; }
-keepnames @kotlinx.serialization.Serializable class * implements androidx.navigation3.runtime.NavKey
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ku_stacks.ku_ring.navigation

import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey

interface EntryBuilderProvider {
fun EntryProviderScope<NavKey>.provide()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ku_stacks.ku_ring.navigation.keys

import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

@Serializable data object SplashKey : NavKey
@Serializable data object OnboardingKey : NavKey
@Serializable data class AuthFlowKey(val entryPoint: String) : NavKey
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ku_stacks.ku_ring.navigation.keys

import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

@Serializable data class NoticeWebKey(
val url: String,
val articleId: String,
val id: String,
val category: String,
val subject: String,
) : NavKey
Comment on lines +6 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

id 필드 타입 불일치: String vs Int

NoticeWebKey.idString 타입으로 선언되었지만, 기존 WebViewNotice.idNoticeDetailRoute.NoticeWeb.id는 모두 Int 타입입니다(data/domain/src/main/java/com/ku_stacks/ku_ring/domain/WebViewNotice.ktdomain/navigation/src/main/java/com/ku_stacks/ku_ring/navigation/NoticeDetailRoute.kt 참조).

이 불일치로 인해 변환 시 파싱 오류가 발생하거나 데이터 손실이 있을 수 있습니다. URL 직렬화를 위해 의도적으로 String을 사용한 경우라도, 매핑 로직에서 일관성 있게 처리해야 합니다.

🔧 Int 타입으로 일관성 유지 제안
 `@Serializable` data class NoticeWebKey(
     val url: String,
     val articleId: String,
-    val id: String,
+    val id: Int,
     val category: String,
     val subject: String,
 ) : NavKey
📝 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
@Serializable data class NoticeWebKey(
val url: String,
val articleId: String,
val id: String,
val category: String,
val subject: String,
) : NavKey
`@Serializable` data class NoticeWebKey(
val url: String,
val articleId: String,
val id: Int,
val category: String,
val subject: String,
) : NavKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/navigation/src/main/java/com/ku_stacks/ku_ring/navigation/keys/ContentDetailKeys.kt`
around lines 6 - 12, NoticeWebKey's id is declared as String but must match the
Int type used by WebViewNotice.id and NoticeDetailRoute.NoticeWeb.id; update the
`@Serializable` data class NoticeWebKey to use val id: Int (or, if string is
intentional, add explicit mapping/parse logic wherever NoticeWebKey is
constructed/serialized to consistently convert between String and Int) so that
the types are consistent with WebViewNotice and NoticeDetailRoute and avoid
parsing/serialization errors.


@Serializable data class NotionViewKey(val url: String) : NavKey

@Serializable data object ClubOnboardingKey : NavKey
@Serializable data class ClubDetailKey(val clubId: Int) : NavKey
@Serializable data object ClubSubscriptionKey : NavKey
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ku_stacks.ku_ring.navigation.keys

import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

@Serializable data class MainHubKey(val startTab: String = "Notice") : NavKey
@Serializable data object ArchiveKey : NavKey
@Serializable data object SearchKey : NavKey
@Serializable data object OpenSourceKey : NavKey
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ku_stacks.ku_ring.navigation.keys

import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

@Serializable data object FeedbackKey : NavKey
@Serializable data object EditSubscriptionKey : NavKey
@Serializable data object EditDepartmentsKey : NavKey
@Serializable data object NotificationKey : NavKey
@Serializable data object LibrarySeatKey : NavKey
@Serializable data object KuringBotKey : NavKey
Empty file.
1 change: 1 addition & 0 deletions feature/club/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ android {
}

dependencies {
implementation(projects.core.navigation)
implementation(projects.core.designsystem)
implementation(projects.core.preferences)
implementation(projects.core.ui)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,16 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme
import com.ku_stacks.ku_ring.util.navigateToExternalBrowser
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class ClubDetailActivity : ComponentActivity() {

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

setContent {
KuringTheme {
val context = LocalContext.current

ClubDetailScreen(
onBack = ::finish,
onMoveToRecruitmentLink = context::navigateToExternalBrowser,
modifier = Modifier
.background(KuringTheme.colors.background)
.safeDrawingPadding()
.fillMaxSize(),
)
}
}
val intent = Intent().setClassName(this, "com.ku_stacks.ku_ring.HostActivity")
startActivity(intent)
finish()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.ku_stacks.ku_ring.club.detail

import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme
import com.ku_stacks.ku_ring.navigation.EntryBuilderProvider
import com.ku_stacks.ku_ring.navigation.keys.ClubDetailKey
import com.ku_stacks.ku_ring.util.navigateToExternalBrowser
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet

class ClubDetailEntryBuilder : EntryBuilderProvider {
override fun EntryProviderScope<NavKey>.provide() {
entry<ClubDetailKey> { key ->
val context = LocalContext.current
ClubDetailScreen(
onBack = { (context as? Activity)?.finish() },
onMoveToRecruitmentLink = context::navigateToExternalBrowser,
modifier = Modifier
.background(KuringTheme.colors.background)
.safeDrawingPadding()
.fillMaxSize(),
)
}
}
}

@Module
@InstallIn(SingletonComponent::class)
object ClubDetailEntryBuilderModule {
@Provides
@IntoSet
fun provideClubDetailEntryBuilder(): EntryBuilderProvider = ClubDetailEntryBuilder()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,15 @@ import android.content.Context
import android.content.Intent
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.ui.Modifier
import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class ClubOnboardingActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KuringTheme {
ClubOnboardingScreen(onClose = ::finish, modifier = Modifier.fillMaxSize())
}
}
val intent = Intent().setClassName(this, "com.ku_stacks.ku_ring.HostActivity")
startActivity(intent)
finish()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ku_stacks.ku_ring.club.onboarding

import android.app.Activity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.ku_stacks.ku_ring.navigation.EntryBuilderProvider
import com.ku_stacks.ku_ring.navigation.keys.ClubOnboardingKey
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet

class ClubOnboardingEntryBuilder : EntryBuilderProvider {
override fun EntryProviderScope<NavKey>.provide() {
entry<ClubOnboardingKey> {
val context = LocalContext.current
ClubOnboardingScreen(
onClose = { (context as? Activity)?.finish() },
modifier = Modifier.fillMaxSize(),
)
}
}
}

@Module
@InstallIn(SingletonComponent::class)
object ClubOnboardingEntryBuilderModule {
@Provides
@IntoSet
fun provideClubOnboardingEntryBuilder(): EntryBuilderProvider = ClubOnboardingEntryBuilder()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,15 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class ClubSubscriptionActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KuringTheme {
ClubSubscriptionScreen(
onNavigateUp = ::finish,
onNavigateToClubDetail = { clubId ->
// TODO: 동아리 상세 화면으로 이동하는 로직 구현
},
)
}
}
val intent = Intent().setClassName(this, "com.ku_stacks.ku_ring.HostActivity")
startActivity(intent)
finish()
}

companion object {
Expand Down
Loading