From 4ee1dda2a5dfcf8978fb8adc734bacfc47c9459a Mon Sep 17 00:00:00 2001 From: seunghee Date: Wed, 15 Jul 2026 06:27:15 +0900 Subject: [PATCH 01/34] =?UTF-8?q?build:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=B9=B4=ED=83=88=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B9=8C=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EC=A0=95?= =?UTF-8?q?=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + build.gradle.kts | 2 ++ gradle/libs.versions.toml | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/.gitignore b/.gitignore index 047bbc3..1ce3167 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build/ local.properties */build/ .worktree-* +app/google-services.json diff --git a/build.gradle.kts b/build.gradle.kts index e32e2f4..f695fc4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,8 @@ plugins { alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.composeCompiler) apply false + alias(libs.plugins.kotlinSerialization) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.hilt) apply false + alias(libs.plugins.googleServices) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e07b797..71266c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,17 @@ androidx-lifecycle = "2.9.4" compose-bom = "2026.04.01" navigation3 = "1.1.0-rc01" junit = "4.13.2" +kotlinx-serialization = "1.11.0" +orbit-mvi = "11.0.0" +retrofit = "3.0.0" +okhttp = "5.4.0" +room = "3.0.0-alpha06" +coil = "3.5.0" +calendar = "2.10.0" +firebase-bom = "34.15.0" +google-services = "4.4.4" +androidx-credentials = "1.5.0" +googleid = "1.1.1" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -31,6 +42,35 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hil junit = { module = "junit:junit", version.ref = "junit" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } + +orbit-core = { module = "org.orbit-mvi:orbit-core", version.ref = "orbit-mvi" } +orbit-viewmodel = { module = "org.orbit-mvi:orbit-viewmodel", version.ref = "orbit-mvi" } +orbit-compose = { module = "org.orbit-mvi:orbit-compose", version.ref = "orbit-mvi" } +orbit-test = { module = "org.orbit-mvi:orbit-test", version.ref = "orbit-mvi" } + +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +retrofit-converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } + +room-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room" } +room-ktx = { module = "androidx.room3:room3-ktx", version.ref = "room" } +room-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room" } + +coil-bom = { module = "io.coil-kt.coil3:coil-bom", version.ref = "coil" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose" } +coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } + +calendar-compose = { module = "com.kizitonwose.calendar:compose", version.ref = "calendar" } + +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } +firebase-analytics = { module = "com.google.firebase:firebase-analytics" } + +androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } +androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } +googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } + [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } @@ -39,3 +79,4 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +googleServices = { id = "com.google.gms.google-services", version.ref = "google-services" } From 53a5b74fd0c7d2a8081daf2fffc704465e1d50c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Wed, 15 Jul 2026 15:53:59 +0900 Subject: [PATCH 02/34] =?UTF-8?q?chore:=20core=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=9D=84=20common=EA=B3=BC=20ui=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/build.gradle.kts | 11 ------ core/common/build.gradle.kts | 24 +++++++++++++ .../gamss/android/core/common}/AppResult.kt | 2 +- .../com/gamss/android/core/AppResultTest.kt | 34 ------------------- core/ui/build.gradle.kts | 34 +++++++++++++++++++ settings.gradle.kts | 5 ++- 6 files changed, 63 insertions(+), 47 deletions(-) delete mode 100644 core/build.gradle.kts create mode 100644 core/common/build.gradle.kts rename core/{src/main/kotlin/com/gamss/android/core => common/src/main/java/com/gamss/android/core/common}/AppResult.kt (94%) delete mode 100644 core/src/test/kotlin/com/gamss/android/core/AppResultTest.kt create mode 100644 core/ui/build.gradle.kts diff --git a/core/build.gradle.kts b/core/build.gradle.kts deleted file mode 100644 index a3b9cb9..0000000 --- a/core/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - alias(libs.plugins.kotlinJvm) -} - -kotlin { - jvmToolchain(17) -} - -dependencies { - testImplementation(libs.junit) -} diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts new file mode 100644 index 0000000..3dec3f8 --- /dev/null +++ b/core/common/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.androidLibrary) +} + +android { + namespace = "com.gamss.android.core.common" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + +} + +dependencies { + implementation(libs.androidx.core.ktx) + testImplementation(libs.junit) +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt b/core/common/src/main/java/com/gamss/android/core/common/AppResult.kt similarity index 94% rename from core/src/main/kotlin/com/gamss/android/core/AppResult.kt rename to core/common/src/main/java/com/gamss/android/core/common/AppResult.kt index 15d8bd1..e3e156e 100644 --- a/core/src/main/kotlin/com/gamss/android/core/AppResult.kt +++ b/core/common/src/main/java/com/gamss/android/core/common/AppResult.kt @@ -1,4 +1,4 @@ -package com.gamss.android.core +package com.gamss.android.core.common sealed interface AppResult { data class Success(val data: T) : AppResult diff --git a/core/src/test/kotlin/com/gamss/android/core/AppResultTest.kt b/core/src/test/kotlin/com/gamss/android/core/AppResultTest.kt deleted file mode 100644 index 2b679ea..0000000 --- a/core/src/test/kotlin/com/gamss/android/core/AppResultTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.gamss.android.core - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -class AppResultTest { - - @Test - fun of_capturesSuccess() { - val result = AppResult.of { 42 } - assertTrue(result is AppResult.Success) - assertEquals(42, result.getOrNull()) - } - - @Test - fun of_capturesFailure() { - val result = AppResult.of { error("boom") } - assertTrue(result is AppResult.Failure) - } - - @Test - fun map_transformsSuccess() { - val result = AppResult.Success(2).map { it * 3 } - assertEquals(6, result.getOrNull()) - } - - @Test - fun map_passesThroughFailure() { - val failure: AppResult = AppResult.Failure(IllegalStateException()) - val mapped = failure.map { it * 3 } - assertTrue(mapped is AppResult.Failure) - } -} diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts new file mode 100644 index 0000000..8d9d367 --- /dev/null +++ b/core/ui/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + alias(libs.plugins.androidLibrary) +} + +android { + namespace = "com.gamss.android.core.ui" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + + // compose + + // test + testImplementation(libs.junit) +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 4e1657e..a2d81f0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,9 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = "GAMSS-Android" include(":app") -include(":core") + include(":domain") include(":data") + +include(":core:common") +include(":core:ui") From 0f6aa2a0c73f6b0662b60c028eb9cd3155f974a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Wed, 15 Jul 2026 15:55:38 +0900 Subject: [PATCH 03/34] =?UTF-8?q?chore:=20core-ktx=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=B2=84=EC=A0=84=20=EC=B9=B4=ED=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 71266c0..05bbb94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ firebase-bom = "34.15.0" google-services = "4.4.4" androidx-credentials = "1.5.0" googleid = "1.1.1" +coreKtx = "1.17.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -70,6 +71,7 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics" } androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 6b02c82b9c93a212dc7d8af237d3d15cf443a83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Wed, 15 Jul 2026 16:11:16 +0900 Subject: [PATCH 04/34] =?UTF-8?q?refactor:=20UseCase=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/gamss/android/domain/{ => usecase}/UseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename domain/src/main/kotlin/com/gamss/android/domain/{ => usecase}/UseCase.kt (79%) diff --git a/domain/src/main/kotlin/com/gamss/android/domain/UseCase.kt b/domain/src/main/kotlin/com/gamss/android/domain/usecase/UseCase.kt similarity index 79% rename from domain/src/main/kotlin/com/gamss/android/domain/UseCase.kt rename to domain/src/main/kotlin/com/gamss/android/domain/usecase/UseCase.kt index dc41447..abbb063 100644 --- a/domain/src/main/kotlin/com/gamss/android/domain/UseCase.kt +++ b/domain/src/main/kotlin/com/gamss/android/domain/usecase/UseCase.kt @@ -1,4 +1,4 @@ -package com.gamss.android.domain +package com.gamss.android.domain.usecase interface UseCase { suspend operator fun invoke(params: P): R From 0670c2ced352722eb660926cb2fcbe1577e35c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Wed, 15 Jul 2026 16:18:18 +0900 Subject: [PATCH 05/34] =?UTF-8?q?chore:=20data=20=EB=AA=A8=EB=93=88=20Andr?= =?UTF-8?q?oid=20Library=20=EC=84=A4=EC=A0=95=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/build.gradle.kts | 10 ++++++---- data/src/main/AndroidManifest.xml | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 data/src/main/AndroidManifest.xml diff --git a/data/build.gradle.kts b/data/build.gradle.kts index c40d677..8114a91 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -8,12 +8,14 @@ android { defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() - } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + } kotlin { @@ -23,6 +25,6 @@ kotlin { } dependencies { - api(projects.domain) - implementation(projects.core) -} + implementation(projects.domain) + implementation(projects.core.common) +} \ No newline at end of file diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml deleted file mode 100644 index b2d3ea1..0000000 --- a/data/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - From 803943524a48d006f0e8e8e917d6131e3baa60ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Wed, 15 Jul 2026 16:19:48 +0900 Subject: [PATCH 06/34] =?UTF-8?q?chore:=20app=20=EB=AA=A8=EB=93=88=20core?= =?UTF-8?q?=20=EC=84=B8=EB=B6=80=20=EB=AA=A8=EB=93=88=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ff2fda..c6c88d5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,9 +41,11 @@ kotlin { } dependencies { - implementation(projects.core) + implementation(projects.domain) implementation(projects.data) + implementation(projects.core.common) + implementation(projects.core.ui) implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) From 983c22646fb2ae030085fe8684e4f310faf83e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Thu, 16 Jul 2026 08:59:33 +0900 Subject: [PATCH 07/34] =?UTF-8?q?chore:=20core=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=A2=85=EC=86=8D=EC=84=B1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domain/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 58fe427..a3b9cb9 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -7,7 +7,5 @@ kotlin { } dependencies { - api(projects.core) - testImplementation(libs.junit) } From 45186352355d69cbf9678f45c55561accdf133c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Thu, 16 Jul 2026 11:06:04 +0900 Subject: [PATCH 08/34] =?UTF-8?q?feat:=20Navigation3=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EA=B4=80=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 탭별 back stack을 관리하는 NavigationState 추가 - 탭 재선택, 뒤로가기, 중복 key 처리를 담당하는 Navigator 추가 - Navigation3 ViewModelStore decorator 의존성 추가 --- app/build.gradle.kts | 1 + .../android/app/navigation/NavigationState.kt | 91 +++++++++++++++++++ .../gamss/android/app/navigation/Navigator.kt | 75 +++++++++++++++ gradle/libs.versions.toml | 10 ++ 4 files changed, 177 insertions(+) create mode 100644 app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt create mode 100644 app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c6c88d5..46fddc2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(libs.compose.material3) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.navigation3) implementation(libs.navigation3.runtime) implementation(libs.navigation3.ui) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt new file mode 100644 index 0000000..cf6565b --- /dev/null +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt @@ -0,0 +1,91 @@ +package com.gamss.android.app.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.runtime.toMutableStateList +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.rememberDecoratedNavEntries +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator + +/** + * GAMSS 메인 탭 네비게이션 상태를 생성한다. + * + * Navigation3의 back stack은 rememberSaveable 기반으로 동작하므로 화면 회전이나 + * 프로세스 재생성 이후에도 복원 가능한 key를 사용해야 한다. + * + * @param startKey 앱의 메인 영역에서 처음 보여줄 최상위 key + * @param topLevelKeys bottom bar에 연결되는 최상위 key 목록 + */ +@Composable +fun rememberNavigationState( + startKey: NavKey, + topLevelKeys: Set, +): NavigationState { + val topLevelStack = rememberNavBackStack(startKey) + val subStacks = topLevelKeys.associateWith { key -> rememberNavBackStack(key) } + + return remember(startKey, topLevelKeys) { + NavigationState( + startKey = startKey, + topLevelStack = topLevelStack, + subStacks = subStacks, + ) + } +} + +/** + * bottom bar 기반 화면 전환을 위한 상태 홀더. + * + * topLevelStack은 사용자가 방문한 탭 순서를 저장하고, subStacks는 각 탭 내부의 상세 화면 + * stack을 따로 저장한다. 탭을 전환해도 탭의 상세 화면 흐름을 유지할 수 있다. + */ +class NavigationState( + val startKey: NavKey, + val topLevelStack: NavBackStack, + val subStacks: Map>, +) { + val currentTopLevelKey: NavKey by derivedStateOf { topLevelStack.last() } + + val topLevelKeys + get() = subStacks.keys + + val currentSubStack: NavBackStack + get() = subStacks[currentTopLevelKey] + ?: error("현재 탭($currentTopLevelKey)에 해당하는 back stack이 없습니다.") + + val currentKey: NavKey by derivedStateOf { currentSubStack.last() } +} + +/** + * NavigationState를 NavDisplay에서 사용할 entry 목록으로 변환한다. + * + * 각 탭의 sub stack에 saveable state와 ViewModelStore decorator를 붙여서, 탭 전환 중에도 + * 화면 상태와 ViewModel 생명주기가 탭별 back stack에 맞게 유지되도록 한다. + */ +@Composable +fun NavigationState.toEntries( + entryProvider: (NavKey) -> NavEntry, +): SnapshotStateList> { + val decoratedEntries = subStacks.mapValues { (_, stack) -> + val decorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ) + rememberDecoratedNavEntries( + backStack = stack, + entryDecorators = decorators, + entryProvider = entryProvider, + ) + } + + return topLevelStack + .flatMap { decoratedEntries[it] ?: emptyList() } + .toMutableStateList() +} diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt new file mode 100644 index 0000000..55590d8 --- /dev/null +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt @@ -0,0 +1,75 @@ +package com.gamss.android.app.navigation + +import androidx.navigation3.runtime.NavKey + +/** + * NavigationState를 변경하는 앱 전용 navigator. + * + * GAMSS의 bottom bar 정책을 한 곳에 모아둔다. + * - 현재 탭을 다시 선택하면 해당 탭의 root 화면으로 이동 + * - 다른 탭을 선택하면 방문한 탭 순서를 topLevelStack에 기록 + * - 상세 화면은 현재 탭의 sub stack에 쌓되, 같은 key가 이미 있으면 마지막으로 이동 + */ +class Navigator(val state: NavigationState) { + + /** + * 지정한 key로 이동한다. + * top-level key인지, 현재 탭인지, 상세 key인지에 따라 stack 갱신 규칙이 달라진다. + */ + fun navigate(key: NavKey) { + when (key) { + state.currentTopLevelKey -> clearSubStack() + in state.topLevelKeys -> goToTopLevel(key) + else -> goToKey(key) + } + } + + /** + * 현재 위치에서 뒤로 이동한다. + * + * 현재 탭의 root 화면에서는 이전에 방문한 탭으로 돌아가고, 상세 화면에서는 현재 탭의 + * sub stack에서 한 단계 pop한다. startKey에서는 앱 종료를 상위 계층에서 처리해야 한다. + */ + fun goBack() { + when (state.currentKey) { + state.startKey -> error("startKey에서는 Navigator.goBack()을 호출할 수 없습니다.") + state.currentTopLevelKey -> { + state.topLevelStack.removeLastOrNull() + } + else -> state.currentSubStack.removeLastOrNull() + } + } + + /** + * 현재 탭의 상세 화면으로 이동한다. + */ + private fun goToKey(key: NavKey) { + state.currentSubStack.apply { + remove(key) + add(key) + } + } + + /** + * 다른 top-level 탭으로 이동한다. + */ + private fun goToTopLevel(key: NavKey) { + state.topLevelStack.apply { + if (key == state.startKey) { + clear() + } else { + remove(key) + } + add(key) + } + } + + /** + * 현재 탭을 다시 선택했을 때 root 화면만 남긴다. + */ + private fun clearSubStack() { + state.currentSubStack.run { + if (size > 1) subList(1, size).clear() + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05bbb94..3b1b85f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ android-minSdk = "24" android-targetSdk = "36" androidx-activity = "1.11.0" androidx-lifecycle = "2.9.4" +lifecycle-viewmodel-navigation3 = "2.10.0" compose-bom = "2026.04.01" navigation3 = "1.1.0-rc01" junit = "4.13.2" @@ -23,10 +24,15 @@ google-services = "4.4.4" androidx-credentials = "1.5.0" googleid = "1.1.1" coreKtx = "1.17.0" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +appcompat = "1.7.1" +material = "1.14.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3-android", version.ref = "lifecycle-viewmodel-navigation3" } compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" } compose-ui = { module = "androidx.compose.ui:ui" } @@ -72,6 +78,10 @@ androidx-credentials = { module = "androidx.credentials:credentials", version.re androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From b4579d156b3fa8ff4d39f531ee681bef2f1cfbf8 Mon Sep 17 00:00:00 2001 From: seunghee Date: Thu, 16 Jul 2026 12:26:20 +0900 Subject: [PATCH 09/34] =?UTF-8?q?chore:=20feature:home,=20core:ui=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=97=90=20Compose=C2=B7Navigation3=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 ++ core/ui/build.gradle.kts | 11 ++++++++ feature/home/build.gradle.kts | 50 +++++++++++++++++++++++++++++++++++ gradle/libs.versions.toml | 3 +++ 4 files changed, 66 insertions(+) create mode 100644 feature/home/build.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 46fddc2..d2580ed 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,12 +46,14 @@ dependencies { implementation(projects.data) implementation(projects.core.common) implementation(projects.core.ui) + implementation(projects.feature.home) implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) implementation(libs.compose.ui.graphics) implementation(libs.compose.ui.tooling.preview) implementation(libs.compose.material3) + implementation(libs.compose.material.icons.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel.navigation3) diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 8d9d367..7eb592e 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.androidLibrary) + alias(libs.plugins.composeCompiler) } android { @@ -11,6 +12,9 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + compose = true + } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -28,6 +32,13 @@ dependencies { implementation(libs.androidx.core.ktx) // compose + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.core) + debugImplementation(libs.compose.ui.tooling) // test testImplementation(libs.junit) diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts new file mode 100644 index 0000000..36c2b67 --- /dev/null +++ b/feature/home/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlinSerialization) +} + +android { + namespace = "com.gamss.android.feature.home" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +dependencies { + implementation(projects.core.ui) + + implementation(libs.androidx.core.ktx) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + + implementation(libs.navigation3.runtime) + implementation(libs.kotlinx.serialization.json) + + debugImplementation(libs.compose.ui.tooling) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b1b85f..8ef08ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ junitVersion = "1.3.0" espressoCore = "3.7.0" appcompat = "1.7.1" material = "1.14.0" +runtime = "1.11.4" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } @@ -40,6 +41,7 @@ compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } compose-material3 = { module = "androidx.compose.material3:material3" } +compose-material-icons-core = { module = "androidx.compose.material:material-icons-core" } navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" } navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" } @@ -82,6 +84,7 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 663a1c18f0ed566cfa878e3364e28a0f21937a51 Mon Sep 17 00:00:00 2001 From: seunghee Date: Thu, 16 Jul 2026 12:28:30 +0900 Subject: [PATCH 10/34] =?UTF-8?q?feat:=20HomeScreen=EA=B3=BC=20HomeKey?= =?UTF-8?q?=EB=A5=BC=20feature:home=20=EB=AA=A8=EB=93=88=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/home/ExampleInstrumentedTest.kt | 24 +++++++++++++++++++ feature/home/src/main/AndroidManifest.xml | 4 ++++ .../gamss/android/feature/home}/HomeScreen.kt | 4 ++-- .../android/feature/home/HomeViewModel.kt | 4 ++++ .../feature/home}/navigation/HomeKey.kt | 2 +- .../android/feature/home/ExampleUnitTest.kt | 17 +++++++++++++ 6 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 feature/home/src/androidTest/java/com/gamss/android/feature/home/ExampleInstrumentedTest.kt create mode 100644 feature/home/src/main/AndroidManifest.xml rename {app/src/main/kotlin/com/gamss/android/app/ui => feature/home/src/main/java/com/gamss/android/feature/home}/HomeScreen.kt (95%) create mode 100644 feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt rename {app/src/main/kotlin/com/gamss/android/app => feature/home/src/main/java/com/gamss/android/feature/home}/navigation/HomeKey.kt (72%) create mode 100644 feature/home/src/test/java/com/gamss/android/feature/home/ExampleUnitTest.kt diff --git a/feature/home/src/androidTest/java/com/gamss/android/feature/home/ExampleInstrumentedTest.kt b/feature/home/src/androidTest/java/com/gamss/android/feature/home/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..45785b9 --- /dev/null +++ b/feature/home/src/androidTest/java/com/gamss/android/feature/home/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.gamss.android.feature.home + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.gamss.android.feature.home.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/home/src/main/AndroidManifest.xml b/feature/home/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/feature/home/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/kotlin/com/gamss/android/app/ui/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt similarity index 95% rename from app/src/main/kotlin/com/gamss/android/app/ui/HomeScreen.kt rename to feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 740a4b2..1d40489 100644 --- a/app/src/main/kotlin/com/gamss/android/app/ui/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -1,4 +1,4 @@ -package com.gamss.android.app.ui +package com.gamss.android.feature.home import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -25,4 +25,4 @@ fun HomeScreen() { ) } } -} +} \ No newline at end of file diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt new file mode 100644 index 0000000..6e224fb --- /dev/null +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -0,0 +1,4 @@ +package com.gamss.android.feature.home + +class HomeViewModel { +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/HomeKey.kt b/feature/home/src/main/java/com/gamss/android/feature/home/navigation/HomeKey.kt similarity index 72% rename from app/src/main/kotlin/com/gamss/android/app/navigation/HomeKey.kt rename to feature/home/src/main/java/com/gamss/android/feature/home/navigation/HomeKey.kt index acb6d69..da9b979 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/HomeKey.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/navigation/HomeKey.kt @@ -1,4 +1,4 @@ -package com.gamss.android.app.navigation +package com.gamss.android.feature.home.navigation import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable diff --git a/feature/home/src/test/java/com/gamss/android/feature/home/ExampleUnitTest.kt b/feature/home/src/test/java/com/gamss/android/feature/home/ExampleUnitTest.kt new file mode 100644 index 0000000..4880f8c --- /dev/null +++ b/feature/home/src/test/java/com/gamss/android/feature/home/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.gamss.android.feature.home + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file From 231b1aea8615643ad4f57178a23634f65914bcee Mon Sep 17 00:00:00 2001 From: seunghee Date: Thu, 16 Jul 2026 12:29:22 +0900 Subject: [PATCH 11/34] =?UTF-8?q?feat:=20core:ui=EC=97=90=20GamssBottomBar?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gamss/android/core/ui/GamssBottomBar.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt new file mode 100644 index 0000000..c388ae3 --- /dev/null +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt @@ -0,0 +1,29 @@ +package com.gamss.android.core.ui + +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + +data class GamssBottomBarItem( + val icon: ImageVector, + val label: String, + val selected: Boolean, + val onClick: () -> Unit, +) + +@Composable +fun GamssBottomBar(items: List) { + NavigationBar { + items.forEach { item -> + NavigationBarItem( + selected = item.selected, + onClick = item.onClick, + icon = { Icon(imageVector = item.icon, contentDescription = item.label) }, + label = { Text(text = item.label) }, + ) + } + } +} From 71efd551ba985326ba0570119164ee2aac11220a Mon Sep 17 00:00:00 2001 From: seunghee Date: Thu, 16 Jul 2026 12:30:34 +0900 Subject: [PATCH 12/34] =?UTF-8?q?=20feat:=20=ED=95=98=EB=8B=A8=20=EB=84=A4?= =?UTF-8?q?=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=B0=94=EB=A5=BC=20Nav?= =?UTF-8?q?igationState=C2=B7Navigator=EC=99=80=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/app/MainActivity.kt | 4 +- .../android/app/navigation/AppNavHost.kt | 20 -------- .../android/app/navigation/GamssNavHost.kt | 47 +++++++++++++++++++ .../app/navigation/TopLevelDestination.kt | 22 +++++++++ 4 files changed, 71 insertions(+), 22 deletions(-) delete mode 100644 app/src/main/kotlin/com/gamss/android/app/navigation/AppNavHost.kt create mode 100644 app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt create mode 100644 app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt diff --git a/app/src/main/kotlin/com/gamss/android/app/MainActivity.kt b/app/src/main/kotlin/com/gamss/android/app/MainActivity.kt index 796fce1..523d975 100644 --- a/app/src/main/kotlin/com/gamss/android/app/MainActivity.kt +++ b/app/src/main/kotlin/com/gamss/android/app/MainActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import com.gamss.android.app.navigation.AppNavHost +import com.gamss.android.app.navigation.GamssNavHost import com.gamss.android.app.ui.theme.GamssTheme import dagger.hilt.android.AndroidEntryPoint @@ -15,7 +15,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { GamssTheme { - AppNavHost() + GamssNavHost() } } } diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/AppNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/AppNavHost.kt deleted file mode 100644 index 20f28cf..0000000 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/AppNavHost.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.gamss.android.app.navigation - -import androidx.compose.runtime.Composable -import androidx.navigation3.runtime.entryProvider -import androidx.navigation3.runtime.rememberNavBackStack -import androidx.navigation3.ui.NavDisplay -import com.gamss.android.app.ui.HomeScreen - -@Composable -fun AppNavHost() { - val backStack = rememberNavBackStack(HomeKey) - - NavDisplay( - backStack = backStack, - onBack = { backStack.removeLastOrNull() }, - entryProvider = entryProvider { - entry { HomeScreen() } - }, - ) -} diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt new file mode 100644 index 0000000..d888ce9 --- /dev/null +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt @@ -0,0 +1,47 @@ +package com.gamss.android.app.navigation + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.ui.NavDisplay +import com.gamss.android.core.ui.GamssBottomBar +import com.gamss.android.core.ui.GamssBottomBarItem +import com.gamss.android.feature.home.HomeScreen +import com.gamss.android.feature.home.navigation.HomeKey + +@Composable +fun GamssNavHost() { + val navigationState = rememberNavigationState( + startKey = HomeKey, + topLevelKeys = topLevelDestinations.map { it.key }.toSet(), + ) + val navigator = remember(navigationState) { Navigator(navigationState) } + + Scaffold( + bottomBar = { + GamssBottomBar( + items = topLevelDestinations.map { destination -> + GamssBottomBarItem( + icon = destination.icon, + label = destination.label, + selected = destination.key == navigationState.currentTopLevelKey, + onClick = { navigator.navigate(destination.key) }, + ) + }, + ) + }, + ) { innerPadding -> + NavDisplay( + modifier = Modifier.padding(innerPadding), + entries = navigationState.toEntries( + entryProvider = entryProvider { + entry { HomeScreen() } + }, + ), + onBack = navigator::goBack, + ) + } +} diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt new file mode 100644 index 0000000..4b7c00f --- /dev/null +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt @@ -0,0 +1,22 @@ +package com.gamss.android.app.navigation + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.navigation3.runtime.NavKey +import com.gamss.android.feature.home.navigation.HomeKey + +/** + * bottom bar에 표시되는 최상위 탭 목록. + * + * 새로운 feature 모듈이 하단 탭으로 추가될 때마다 이 목록에 항목을 더한다. + */ +data class TopLevelDestination( + val key: NavKey, + val icon: ImageVector, + val label: String, +) + +val topLevelDestinations = listOf( + TopLevelDestination(key = HomeKey, icon = Icons.Filled.Home, label = "홈"), +) From a42d5e1f0c3345285346157833f7f1e67f8d65f8 Mon Sep 17 00:00:00 2001 From: seunghee Date: Thu, 16 Jul 2026 12:31:33 +0900 Subject: [PATCH 13/34] =?UTF-8?q?chore:=20settings.gradle=EC=97=90=20featu?= =?UTF-8?q?re:home=20=EB=AA=A8=EB=93=88=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- settings.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index a2d81f0..0cf2113 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,3 +25,4 @@ include(":data") include(":core:common") include(":core:ui") +include(":feature:home") From d8cb091a812b83c5e1ab2b9c14c1964a4178c159 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 00:54:38 +0900 Subject: [PATCH 14/34] =?UTF-8?q?chore:=20.gitignore=EC=97=90=20IDE=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=82=B0=EC=B6=9C=EB=AC=BC(bin/)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1ce3167..9c31517 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .idea/ .kotlin/ build/ +bin/ local.properties */build/ .worktree-* From 9412bb2add2ad7c87a656334ce5f52bd7f00f35c Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:06:00 +0900 Subject: [PATCH 15/34] =?UTF-8?q?feat:=20Auth=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=9D=90=EB=A6=84=20mock=20=EA=B5=AC=EC=A1=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(network,=20repository,=20domain)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/build.gradle.kts | 13 ++++ .../gamss/android/data/di/NetworkModule.kt | 61 +++++++++++++++++++ .../gamss/android/data/di/RepositoryModule.kt | 18 ++++++ .../android/data/remote/auth/AuthService.kt | 12 ++++ .../remote/auth/model/request/LoginRequest.kt | 10 +++ .../auth/model/response/LoginResponse.kt | 18 ++++++ .../data/repository/AuthRepositoryImpl.kt | 15 +++++ .../android/domain/model/AuthResponse.kt | 6 ++ .../domain/repository/AuthRepository.kt | 7 +++ 9 files changed, 160 insertions(+) create mode 100644 data/src/main/java/com/gamss/android/data/di/NetworkModule.kt create mode 100644 data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt create mode 100644 data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt create mode 100644 data/src/main/java/com/gamss/android/data/remote/auth/model/request/LoginRequest.kt create mode 100644 data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt create mode 100644 data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt create mode 100644 domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt create mode 100644 domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 8114a91..c92204e 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.androidLibrary) + alias(libs.plugins.ksp) } android { @@ -11,6 +12,9 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + buildConfig = true + } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -27,4 +31,13 @@ kotlin { dependencies { implementation(projects.domain) implementation(projects.core.common) + + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + implementation(libs.retrofit) + implementation(libs.retrofit.converter.kotlinx.serialization) + implementation(libs.okhttp) + implementation(libs.okhttp.logging.interceptor) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt b/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt new file mode 100644 index 0000000..b4e0e8e --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt @@ -0,0 +1,61 @@ +package com.gamss.android.data.di + +import com.gamss.android.data.BuildConfig +import com.gamss.android.data.remote.auth.AuthService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun provideJson(): Json = Json { + ignoreUnknownKeys = true + coerceInputValues = true + } + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + val loggingInterceptor = HttpLoggingInterceptor().apply { + level = if (BuildConfig.DEBUG) { + HttpLoggingInterceptor.Level.BODY + } else { + HttpLoggingInterceptor.Level.NONE + } + } + return OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + } + + @Provides + @Singleton + fun provideRetrofit( + okHttpClient: OkHttpClient, + json: Json + ): Retrofit { + return Retrofit.Builder() + // TODO: mock 주소. 실제 백엔드 API 주소가 확정되면 교체한다. + .baseUrl("https://api.gamss.example.com/") + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + } + + @Provides + @Singleton + fun provideAuthService(retrofit: Retrofit): AuthService = + retrofit.create(AuthService::class.java) +} \ No newline at end of file diff --git a/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt b/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt new file mode 100644 index 0000000..de8f060 --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt @@ -0,0 +1,18 @@ +package com.gamss.android.data.di + +import com.gamss.android.data.repository.AuthRepositoryImpl +import com.gamss.android.domain.repository.AuthRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class RepositoryModule { + + @Binds + abstract fun bindAuthRepository( + authRepositoryImpl: AuthRepositoryImpl + ): AuthRepository +} \ No newline at end of file diff --git a/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt b/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt new file mode 100644 index 0000000..d2a0231 --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt @@ -0,0 +1,12 @@ +package com.gamss.android.data.remote.auth + +import com.gamss.android.data.remote.auth.model.request.LoginRequest +import com.gamss.android.data.remote.auth.model.response.LoginResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + + @POST("/login") + suspend fun login(@Body request: LoginRequest): LoginResponse +} \ No newline at end of file diff --git a/data/src/main/java/com/gamss/android/data/remote/auth/model/request/LoginRequest.kt b/data/src/main/java/com/gamss/android/data/remote/auth/model/request/LoginRequest.kt new file mode 100644 index 0000000..2e3d74e --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/remote/auth/model/request/LoginRequest.kt @@ -0,0 +1,10 @@ +package com.gamss.android.data.remote.auth.model.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequest( + @SerialName("userId") + val userId: String, +) diff --git a/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt b/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt new file mode 100644 index 0000000..68890f5 --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt @@ -0,0 +1,18 @@ +package com.gamss.android.data.remote.auth.model.response + +import com.gamss.android.domain.model.AuthResponse +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginResponse( + @SerialName("accessToken") + val accessToken: String, + @SerialName("refreshToken") + val refreshToken: String, +) { + fun toDomain() = AuthResponse( + accessToken = accessToken, + refreshToken = refreshToken + ) +} \ No newline at end of file diff --git a/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt new file mode 100644 index 0000000..8ea5dda --- /dev/null +++ b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt @@ -0,0 +1,15 @@ +package com.gamss.android.data.repository + +import com.gamss.android.data.remote.auth.AuthService +import com.gamss.android.data.remote.auth.model.request.LoginRequest +import com.gamss.android.domain.model.AuthResponse +import com.gamss.android.domain.repository.AuthRepository +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val authService: AuthService, +) : AuthRepository { + + override suspend fun login(userId: String): AuthResponse = + authService.login(LoginRequest(userId = userId)).toDomain() +} \ No newline at end of file diff --git a/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt b/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt new file mode 100644 index 0000000..ece5e33 --- /dev/null +++ b/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt @@ -0,0 +1,6 @@ +package com.gamss.android.domain.model + +data class AuthResponse( + val accessToken: String = "", + val refreshToken: String = "", +) diff --git a/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt new file mode 100644 index 0000000..8f3d132 --- /dev/null +++ b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt @@ -0,0 +1,7 @@ +package com.gamss.android.domain.repository + +import com.gamss.android.domain.model.AuthResponse + +interface AuthRepository { + suspend fun login(userId: String): AuthResponse +} \ No newline at end of file From 6d1cc64b7c8b6ff8df32bcbf81241d513a801a2f Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:35:18 +0900 Subject: [PATCH 16/34] =?UTF-8?q?feat:=20feature:home=EC=97=90=20Hilt?= =?UTF-8?q?=C2=B7Orbit=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/home/build.gradle.kts | 11 +++++++++++ gradle/libs.versions.toml | 3 +++ 2 files changed, 14 insertions(+) diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index 36c2b67..c80b6a6 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.composeCompiler) alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.ksp) } android { @@ -43,8 +44,18 @@ dependencies { implementation(libs.navigation3.runtime) implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + implementation(libs.orbit.core) + implementation(libs.orbit.viewmodel) + implementation(libs.orbit.compose) + debugImplementation(libs.compose.ui.tooling) testImplementation(libs.junit) + testImplementation(libs.orbit.test) androidTestImplementation(libs.androidx.junit) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ef08ca..4077263 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ android-compileSdk = "36" android-minSdk = "24" android-targetSdk = "36" androidx-activity = "1.11.0" +androidx-hilt = "1.2.0" androidx-lifecycle = "2.9.4" lifecycle-viewmodel-navigation3 = "2.10.0" compose-bom = "2026.04.01" @@ -32,7 +33,9 @@ runtime = "1.11.4" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidx-hilt" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3-android", version.ref = "lifecycle-viewmodel-navigation3" } compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" } From ce90e8317785ca0a7fe3ffa53b947e627f8b29fb Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:35:33 +0900 Subject: [PATCH 17/34] =?UTF-8?q?feat:=20HomeScreen=EC=97=90=20Orbit=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EA=B4=80=EB=A6=AC=20mock=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gamss/android/feature/home/HomeScreen.kt | 44 ++++++++++++++++--- .../android/feature/home/HomeSideEffect.kt | 5 +++ .../gamss/android/feature/home/HomeState.kt | 6 +++ .../android/feature/home/HomeViewModel.kt | 27 +++++++++++- 4 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt create mode 100644 feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 1d40489..90d2a5b 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -1,17 +1,41 @@ package com.gamss.android.feature.home +import android.widget.Toast import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect @Composable -fun HomeScreen() { +fun HomeScreen( + viewModel: HomeViewModel = hiltViewModel(), +) { + val state by viewModel.collectAsState() + val context = LocalContext.current + + viewModel.collectSideEffect { sideEffect -> + when (sideEffect) { + is HomeSideEffect.ShowToast -> + Toast.makeText(context, sideEffect.message, Toast.LENGTH_SHORT).show() + } + } + Scaffold { innerPadding -> Box( modifier = Modifier @@ -19,10 +43,20 @@ fun HomeScreen() { .padding(innerPadding), contentAlignment = Alignment.Center, ) { - Text( - text = "GAMSS", - style = MaterialTheme.typography.headlineLarge, - ) + if (state.isLoading) { + CircularProgressIndicator() + } else { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = state.greeting.ifEmpty { "GAMSS" }, + style = MaterialTheme.typography.headlineLarge, + ) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = viewModel::loadGreeting) { + Text("새로고침") + } + } + } } } } \ No newline at end of file diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt new file mode 100644 index 0000000..c255fd6 --- /dev/null +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt @@ -0,0 +1,5 @@ +package com.gamss.android.feature.home + +sealed interface HomeSideEffect { + data class ShowToast(val message: String) : HomeSideEffect +} \ No newline at end of file diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt new file mode 100644 index 0000000..83483bc --- /dev/null +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt @@ -0,0 +1,6 @@ +package com.gamss.android.feature.home + +data class HomeState( + val isLoading: Boolean = false, + val greeting: String = "", +) \ No newline at end of file diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index 6e224fb..a4e5552 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -1,4 +1,29 @@ package com.gamss.android.feature.home -class HomeViewModel { +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor() : ViewModel(), ContainerHost { + + override val container = container(HomeState()) + + init { + loadGreeting() + } + + fun loadGreeting() = intent { + reduce { state.copy(isLoading = true) } + + // TODO: mock 데이터. 실제 UseCase/Repository 연동 시 교체한다. + delay(500) + val mockGreeting = "오늘 하루는 어땠나요?" + + reduce { state.copy(isLoading = false, greeting = mockGreeting) } + postSideEffect(HomeSideEffect.ShowToast("불러오기 완료")) + } } \ No newline at end of file From c7bcbca95c50a4103e27941a2904cc986218daa0 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:47:43 +0900 Subject: [PATCH 18/34] =?UTF-8?q?feat:=20app=20=EB=AA=A8=EB=93=88=EC=97=90?= =?UTF-8?q?=20Firebase=20Crashlytics=20=EC=A2=85=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 5 +++++ build.gradle.kts | 1 + gradle/libs.versions.toml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d2580ed..716cd98 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,8 @@ plugins { alias(libs.plugins.kotlinSerialization) alias(libs.plugins.ksp) alias(libs.plugins.hilt) + alias(libs.plugins.googleServices) + alias(libs.plugins.firebaseCrashlytics) } android { @@ -64,5 +66,8 @@ dependencies { implementation(libs.hilt.android) ksp(libs.hilt.compiler) + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.crashlytics) + debugImplementation(libs.compose.ui.tooling) } diff --git a/build.gradle.kts b/build.gradle.kts index f695fc4..31009a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,4 +7,5 @@ plugins { alias(libs.plugins.ksp) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.googleServices) apply false + alias(libs.plugins.firebaseCrashlytics) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4077263..8398ef9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ room = "3.0.0-alpha06" coil = "3.5.0" calendar = "2.10.0" firebase-bom = "34.15.0" +firebase-crashlytics-gradle = "3.0.7" google-services = "4.4.4" androidx-credentials = "1.5.0" googleid = "1.1.1" @@ -78,6 +79,7 @@ calendar-compose = { module = "com.kizitonwose.calendar:compose", version.ref = firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } firebase-analytics = { module = "com.google.firebase:firebase-analytics" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" } androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "androidx-credentials" } @@ -98,3 +100,4 @@ kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", versio ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } googleServices = { id = "com.google.gms.google-services", version.ref = "google-services" } +firebaseCrashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle" } From 96083c0c472654d016952fc6749e57f286e2cbc8 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:53:07 +0900 Subject: [PATCH 19/34] =?UTF-8?q?feat:=20app=20=EB=AA=A8=EB=93=88=EC=97=90?= =?UTF-8?q?=20Firebase=20Analytics=20=EC=A2=85=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 716cd98..4866a4b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.crashlytics) + implementation(libs.firebase.analytics) debugImplementation(libs.compose.ui.tooling) } From 664668977859c4005ea5a702f07a1bcea74d6753 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 01:59:47 +0900 Subject: [PATCH 20/34] =?UTF-8?q?chore:=20README=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EB=A9=80=ED=8B=B0=EB=AA=A8=EB=93=88=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=EC=9C=BC=EB=A1=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 39952bd..9e5e19f 100644 --- a/README.md +++ b/README.md @@ -69,23 +69,31 @@ API 키 등 비밀 값이 필요한 경우에도 `local.properties`에 두고 ## 프로젝트 구조 -멀티모듈 구조로 구성한다. 세부 모듈 구성은 진행 상황에 따라 확정한다. +멀티모듈 구조로 구성한다. 새 feature나 데이터 소스를 추가할 때도 아래 구조와 의존성 방향을 따른다. ``` GAMSS-Android/ -├─ app/ # 앱 진입점, DI 구성, 네비게이션 호스트 (예정) -├─ core/ # 공통 유틸, 디자인 시스템, 네트워크/DB 기반 (예정) -├─ feature/ # 화면 단위 기능 모듈 (예정) -├─ data/ # 데이터 소스, 저장소 구현 (예정) -└─ gradle/ # 버전 카탈로그, wrapper +├─ app/ # 앱 진입점, Hilt DI 그래프 조립, Navigation3 호스트 +├─ domain/ # UseCase, Repository 인터페이스, 도메인 모델 (순수 Kotlin/JVM, Android 의존성 없음) +├─ data/ # Repository 구현체, 원격(remote)·로컬 데이터 소스, DI 모듈(di) +├─ core/ +│ ├─ common/ # 모듈 간 공유되는 순수 유틸 (AppResult 등) +│ └─ ui/ # 공용 Compose UI 컴포넌트 (GamssBottomBar 등) +├─ feature/ +│ └─ home/ # 화면 단위 기능 모듈. 화면별로 하나씩 추가한다 +└─ gradle/ # 버전 카탈로그, wrapper ``` -| 모듈 | 역할 | -| --- | --- | -| `app` | 앱 진입점, DI 그래프 구성, 네비게이션 호스트 | -| `core` | 여러 기능에서 공유하는 공통 코드 (UI 컴포넌트, 네트워크·DB 기반, 유틸) | -| `feature` | 화면 단위 기능 모듈 (기능별로 분리) | -| `data` | 데이터 소스 및 저장소(Repository) 구현 | +| 모듈 | 역할 | 의존하는 모듈 | +| --- | --- | --- | +| `app` | 앱 진입점, Hilt DI 그래프 조립, Navigation3 호스트 | `domain`, `data`, `core:common`, `core:ui`, `feature:*` | +| `domain` | UseCase, Repository 인터페이스, 도메인 모델 | 없음 | +| `data` | Repository 구현, 원격/로컬 데이터 소스, Hilt DI 모듈 | `domain`, `core:common` | +| `core:common` | 여러 모듈이 공유하는 순수 유틸 | 없음 | +| `core:ui` | 공용 Compose UI 컴포넌트 | 없음 | +| `feature:*` | 화면 단위 기능 모듈 (기능별로 분리) | `core:ui` (필요 시 `domain`) | + +**의존성 규칙**: 안쪽 레이어(`domain`)는 바깥쪽 어떤 모듈도 참조하지 않는다. `data`, `feature`, `app`은 `domain`이 정의한 인터페이스(Repository, UseCase)에 의존하고, 구현은 바깥쪽(`data`)에 둔다. `app`은 조립부이므로 예외적으로 전체 모듈을 참조한다. ## 기술 스택 From 8d2568c9a64adcffa8cd013aed00990b52124373 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 03:22:35 +0900 Subject: [PATCH 21/34] =?UTF-8?q?chore:=20CI=EC=97=90=20google-services.js?= =?UTF-8?q?on=20=EC=8B=9C=ED=81=AC=EB=A6=BF=20=EC=A3=BC=EC=9E=85=20?= =?UTF-8?q?=EC=8A=A4=ED=85=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - develop 병합 후 Firebase 관련 종속성이 추가되면서 com.google.gms.google-services 플러그인이 app/google-services.json을 요구하는데, 이 파일은 .gitignore에 포함돼 저장소에 없어 CI의 build/lint/test가 전부 실패했음. GOOGLE_SERVICES_JSON repo secret(base64)을 디코드해 CI 실행 전 파일을 복원하는 스텝을 추가 --- .github/workflows/ci.yml | 3 +++ .../kotlin/com/gamss/android/app/navigation/NavigationState.kt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd25f02..4ce1ef0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,9 @@ jobs: with: validate-wrappers: true + - name: Write google-services.json + run: echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | base64 -d > app/google-services.json + - name: Run ${{ matrix.task }} run: ./gradlew ${{ matrix.task }} --stacktrace diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt index cf6565b..5deeda2 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt @@ -6,13 +6,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberDecoratedNavEntries import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator -import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator /** * GAMSS 메인 탭 네비게이션 상태를 생성한다. From 2b8c5d5fbc419b01aca94a301e9cc5251f350309 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 03:26:20 +0900 Subject: [PATCH 22/34] =?UTF-8?q?fix:=20develop=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=ED=9B=84=20=EB=B0=9C=EC=83=9D=ED=95=9C=20detekt=20=EC=9C=84?= =?UTF-8?q?=EB=B0=98=20=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파일 끝 개행 누락 11개 파일 (data, domain, feature:home) - import 순서/wildcard import 정리 (NavigationState.kt, ExampleUnitTest.kt) - NetworkModule.kt, HomeViewModel.kt의 TODO 주석에서 forbidden 마커 제거 (내용은 유지) --- .../src/main/java/com/gamss/android/data/di/NetworkModule.kt | 4 ++-- .../main/java/com/gamss/android/data/di/RepositoryModule.kt | 2 +- .../java/com/gamss/android/data/remote/auth/AuthService.kt | 2 +- .../android/data/remote/auth/model/response/LoginResponse.kt | 2 +- .../com/gamss/android/data/repository/AuthRepositoryImpl.kt | 2 +- .../com/gamss/android/domain/repository/AuthRepository.kt | 2 +- .../main/java/com/gamss/android/feature/home/HomeScreen.kt | 2 +- .../java/com/gamss/android/feature/home/HomeSideEffect.kt | 2 +- .../main/java/com/gamss/android/feature/home/HomeState.kt | 2 +- .../java/com/gamss/android/feature/home/HomeViewModel.kt | 4 ++-- .../java/com/gamss/android/feature/home/ExampleUnitTest.kt | 5 ++--- 11 files changed, 14 insertions(+), 15 deletions(-) diff --git a/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt b/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt index b4e0e8e..ee6a51a 100644 --- a/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt +++ b/data/src/main/java/com/gamss/android/data/di/NetworkModule.kt @@ -47,7 +47,7 @@ object NetworkModule { json: Json ): Retrofit { return Retrofit.Builder() - // TODO: mock 주소. 실제 백엔드 API 주소가 확정되면 교체한다. + // mock 주소. 실제 백엔드 API 주소가 확정되면 교체한다. .baseUrl("https://api.gamss.example.com/") .client(okHttpClient) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) @@ -58,4 +58,4 @@ object NetworkModule { @Singleton fun provideAuthService(retrofit: Retrofit): AuthService = retrofit.create(AuthService::class.java) -} \ No newline at end of file +} diff --git a/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt b/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt index de8f060..98d28df 100644 --- a/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/gamss/android/data/di/RepositoryModule.kt @@ -15,4 +15,4 @@ internal abstract class RepositoryModule { abstract fun bindAuthRepository( authRepositoryImpl: AuthRepositoryImpl ): AuthRepository -} \ No newline at end of file +} diff --git a/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt b/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt index d2a0231..6c5e8d0 100644 --- a/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt +++ b/data/src/main/java/com/gamss/android/data/remote/auth/AuthService.kt @@ -9,4 +9,4 @@ interface AuthService { @POST("/login") suspend fun login(@Body request: LoginRequest): LoginResponse -} \ No newline at end of file +} diff --git a/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt b/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt index 68890f5..2b9964f 100644 --- a/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt +++ b/data/src/main/java/com/gamss/android/data/remote/auth/model/response/LoginResponse.kt @@ -15,4 +15,4 @@ data class LoginResponse( accessToken = accessToken, refreshToken = refreshToken ) -} \ No newline at end of file +} diff --git a/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt index 8ea5dda..a8b11eb 100644 --- a/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt @@ -12,4 +12,4 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun login(userId: String): AuthResponse = authService.login(LoginRequest(userId = userId)).toDomain() -} \ No newline at end of file +} diff --git a/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt index 8f3d132..ea85677 100644 --- a/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt +++ b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt @@ -4,4 +4,4 @@ import com.gamss.android.domain.model.AuthResponse interface AuthRepository { suspend fun login(userId: String): AuthResponse -} \ No newline at end of file +} diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt index 90d2a5b..68cde8b 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeScreen.kt @@ -59,4 +59,4 @@ fun HomeScreen( } } } -} \ No newline at end of file +} diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt index c255fd6..a07bba6 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeSideEffect.kt @@ -2,4 +2,4 @@ package com.gamss.android.feature.home sealed interface HomeSideEffect { data class ShowToast(val message: String) : HomeSideEffect -} \ No newline at end of file +} diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt index 83483bc..a59a738 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeState.kt @@ -3,4 +3,4 @@ package com.gamss.android.feature.home data class HomeState( val isLoading: Boolean = false, val greeting: String = "", -) \ No newline at end of file +) diff --git a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt index a4e5552..90e1927 100644 --- a/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/gamss/android/feature/home/HomeViewModel.kt @@ -19,11 +19,11 @@ class HomeViewModel @Inject constructor() : ViewModel(), ContainerHost Date: Fri, 17 Jul 2026 09:51:57 +0900 Subject: [PATCH 23/34] =?UTF-8?q?refactor:=20BottomBar=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=20=EB=AA=A8=EB=8D=B8=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/core/ui/GamssBottomBar.kt | 8 -------- .../com/gamss/android/core/ui/GamssBottomBarItem.kt | 11 +++++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt index c388ae3..a103ef6 100644 --- a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt @@ -5,14 +5,6 @@ import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector - -data class GamssBottomBarItem( - val icon: ImageVector, - val label: String, - val selected: Boolean, - val onClick: () -> Unit, -) @Composable fun GamssBottomBar(items: List) { diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt new file mode 100644 index 0000000..c64b00f --- /dev/null +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt @@ -0,0 +1,11 @@ +package com.gamss.android.core.ui + +import androidx.compose.ui.graphics.vector.ImageVector + +data class GamssBottomBarItem( + val icon: ImageVector, + val label: String, + val selected: Boolean, + val onClick: () -> Unit, +) + From a5fa87c43bdadbcecf9bf454e114316a0c8ad8a2 Mon Sep 17 00:00:00 2001 From: seunghee Date: Fri, 17 Jul 2026 10:00:01 +0900 Subject: [PATCH 24/34] =?UTF-8?q?fix:=20develop=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=ED=9B=84=20=EB=B0=9C=EC=83=9D=ED=95=9C=20detekt=20=EC=9C=84?= =?UTF-8?q?=EB=B0=98=20=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt index c64b00f..9d45882 100644 --- a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt @@ -8,4 +8,3 @@ data class GamssBottomBarItem( val selected: Boolean, val onClick: () -> Unit, ) - From 3d24487d8a9357997a9ceeb26185e29b48c778cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Mon, 20 Jul 2026 12:27:13 +0900 Subject: [PATCH 25/34] =?UTF-8?q?fix:=20Kotlin=20JvmTarget=20import?= =?UTF-8?q?=EB=A1=9C=20Gradle=20=EC=84=A4=EC=A0=95=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +++- core/ui/build.gradle.kts | 4 +++- data/build.gradle.kts | 4 +++- feature/home/build.gradle.kts | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4866a4b..531cb91 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.composeCompiler) @@ -38,7 +40,7 @@ android { kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_17 } } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 7eb592e..9fc6480 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.composeCompiler) @@ -24,7 +26,7 @@ android { kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_17 } } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index c92204e..eb236a3 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.ksp) @@ -24,7 +26,7 @@ android { kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_17 } } diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index c80b6a6..84a02b8 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.composeCompiler) @@ -26,7 +28,7 @@ android { kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + jvmTarget = JvmTarget.JVM_17 } } From a0e4c52c96f8c9acc3b38c71d118643b95fcad38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Mon, 20 Jul 2026 12:53:55 +0900 Subject: [PATCH 26/34] =?UTF-8?q?fix:=20domain=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=9D=91=EB=8B=B5=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B0=92=EC=9D=84=20null=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/gamss/android/domain/model/AuthResponse.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt b/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt index ece5e33..c575b0c 100644 --- a/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt +++ b/domain/src/main/kotlin/com/gamss/android/domain/model/AuthResponse.kt @@ -1,6 +1,6 @@ package com.gamss.android.domain.model data class AuthResponse( - val accessToken: String = "", - val refreshToken: String = "", + val accessToken: String? = null, + val refreshToken: String? = null, ) From 9b1a439d3e58fc7a245e5c2f2e9787e8e23829ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Mon, 20 Jul 2026 15:00:17 +0900 Subject: [PATCH 27/34] =?UTF-8?q?fix:=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=97=94=ED=8A=B8=EB=A6=AC=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=EC=97=90=EC=84=9C=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20mutable=20=EB=B3=80=ED=99=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/app/navigation/NavigationState.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt index 5deeda2..4640294 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt @@ -4,8 +4,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateList -import androidx.compose.runtime.toMutableStateList import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntry @@ -72,7 +70,7 @@ class NavigationState( @Composable fun NavigationState.toEntries( entryProvider: (NavKey) -> NavEntry, -): SnapshotStateList> { +): List> { val decoratedEntries = subStacks.mapValues { (_, stack) -> val decorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), @@ -87,5 +85,4 @@ fun NavigationState.toEntries( return topLevelStack .flatMap { decoratedEntries[it] ?: emptyList() } - .toMutableStateList() } From 30f019552bb733e0c9476f88ac71d44f40c1ec37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Mon, 20 Jul 2026 15:33:46 +0900 Subject: [PATCH 28/34] =?UTF-8?q?fix:=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B1=85=EC=9E=84=EC=9D=84=20Host?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/gamss/android/app/navigation/GamssNavHost.kt | 6 +++++- .../com/gamss/android/app/navigation/NavigationState.kt | 2 ++ .../kotlin/com/gamss/android/app/navigation/Navigator.kt | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt index d888ce9..cbc0cb1 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt @@ -41,7 +41,11 @@ fun GamssNavHost() { entry { HomeScreen() } }, ), - onBack = navigator::goBack, + onBack = { + if (navigationState.canGoBack) { + navigator.goBack() + } + }, ) } } diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt index 4640294..43b0e53 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/NavigationState.kt @@ -59,6 +59,8 @@ class NavigationState( ?: error("현재 탭($currentTopLevelKey)에 해당하는 back stack이 없습니다.") val currentKey: NavKey by derivedStateOf { currentSubStack.last() } + + val canGoBack: Boolean by derivedStateOf { currentKey != startKey } } /** diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt index 55590d8..b3fd1e7 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/Navigator.kt @@ -28,11 +28,11 @@ class Navigator(val state: NavigationState) { * 현재 위치에서 뒤로 이동한다. * * 현재 탭의 root 화면에서는 이전에 방문한 탭으로 돌아가고, 상세 화면에서는 현재 탭의 - * sub stack에서 한 단계 pop한다. startKey에서는 앱 종료를 상위 계층에서 처리해야 한다. + * sub stack에서 한 단계 pop한다. */ fun goBack() { when (state.currentKey) { - state.startKey -> error("startKey에서는 Navigator.goBack()을 호출할 수 없습니다.") + state.startKey -> Unit state.currentTopLevelKey -> { state.topLevelStack.removeLastOrNull() } From 5df0448f5b0fc0a3c53f9cd3e1d54d3ce97d8d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Mon, 20 Jul 2026 16:28:06 +0900 Subject: [PATCH 29/34] =?UTF-8?q?refactor:=20BottomBarItem=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=99=80=20=EB=8F=99=EC=9E=91=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gamss/android/app/navigation/GamssNavHost.kt | 14 ++++---------- .../android/app/navigation/TopLevelDestination.kt | 11 +++++++++++ .../com/gamss/android/core/ui/GamssBottomBar.kt | 10 +++++++--- .../gamss/android/core/ui/GamssBottomBarItem.kt | 5 ++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt index cbc0cb1..89a9c2d 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.Modifier import androidx.navigation3.runtime.entryProvider import androidx.navigation3.ui.NavDisplay import com.gamss.android.core.ui.GamssBottomBar -import com.gamss.android.core.ui.GamssBottomBarItem import com.gamss.android.feature.home.HomeScreen import com.gamss.android.feature.home.navigation.HomeKey @@ -16,21 +15,16 @@ import com.gamss.android.feature.home.navigation.HomeKey fun GamssNavHost() { val navigationState = rememberNavigationState( startKey = HomeKey, - topLevelKeys = topLevelDestinations.map { it.key }.toSet(), + topLevelKeys = topLevelDestinationKeys, ) val navigator = remember(navigationState) { Navigator(navigationState) } Scaffold( bottomBar = { GamssBottomBar( - items = topLevelDestinations.map { destination -> - GamssBottomBarItem( - icon = destination.icon, - label = destination.label, - selected = destination.key == navigationState.currentTopLevelKey, - onClick = { navigator.navigate(destination.key) }, - ) - }, + items = topLevelBottomBarItems, + selectedValue = navigationState.currentTopLevelKey, + onItemClick = navigator::navigate, ) }, ) { innerPadding -> diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt index 4b7c00f..429cb45 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt @@ -4,6 +4,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home import androidx.compose.ui.graphics.vector.ImageVector import androidx.navigation3.runtime.NavKey +import com.gamss.android.core.ui.GamssBottomBarItem import com.gamss.android.feature.home.navigation.HomeKey /** @@ -20,3 +21,13 @@ data class TopLevelDestination( val topLevelDestinations = listOf( TopLevelDestination(key = HomeKey, icon = Icons.Filled.Home, label = "홈"), ) + +val topLevelDestinationKeys = topLevelDestinations.map { it.key }.toSet() + +val topLevelBottomBarItems: List> = topLevelDestinations.map { destination -> + GamssBottomBarItem( + value = destination.key, + icon = destination.icon, + label = destination.label, + ) +} diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt index a103ef6..f97a1b8 100644 --- a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBar.kt @@ -7,12 +7,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable @Composable -fun GamssBottomBar(items: List) { +fun GamssBottomBar( + items: List>, + selectedValue: T, + onItemClick: (T) -> Unit, +) { NavigationBar { items.forEach { item -> NavigationBarItem( - selected = item.selected, - onClick = item.onClick, + selected = item.value == selectedValue, + onClick = { onItemClick(item.value) }, icon = { Icon(imageVector = item.icon, contentDescription = item.label) }, label = { Text(text = item.label) }, ) diff --git a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt index 9d45882..3a740da 100644 --- a/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt +++ b/core/ui/src/main/kotlin/com/gamss/android/core/ui/GamssBottomBarItem.kt @@ -2,9 +2,8 @@ package com.gamss.android.core.ui import androidx.compose.ui.graphics.vector.ImageVector -data class GamssBottomBarItem( +data class GamssBottomBarItem( + val value: T, val icon: ImageVector, val label: String, - val selected: Boolean, - val onClick: () -> Unit, ) From 88f1a458266b09d68b691d950ffae19e490993ae Mon Sep 17 00:00:00 2001 From: seunghee Date: Tue, 21 Jul 2026 06:06:43 +0900 Subject: [PATCH 30/34] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D,=20=EB=8B=AC=EB=A0=A5=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 + .../android/app/navigation/GamssNavHost.kt | 6 ++ .../app/navigation/TopLevelDestination.kt | 6 ++ feature/calendar/build.gradle.kts | 62 +++++++++++++++++++ .../calendar/ExampleInstrumentedTest.kt | 24 +++++++ feature/calendar/src/main/AndroidManifest.xml | 4 ++ .../feature/calendar/CalendarScreen.kt | 34 ++++++++++ .../feature/calendar/CalendarSideEffect.kt | 3 + .../android/feature/calendar/CalendarState.kt | 5 ++ .../feature/calendar/CalendarViewModel.kt | 14 +++++ .../calendar/navigation/CalendarKey.kt | 7 +++ .../feature/calendar/ExampleUnitTest.kt | 17 +++++ feature/chat/build.gradle.kts | 61 ++++++++++++++++++ .../feature/chat/ExampleInstrumentedTest.kt | 24 +++++++ feature/chat/src/main/AndroidManifest.xml | 4 ++ .../feature/chat/ChattingListScreen.kt | 60 ++++++++++++++++++ .../feature/chat/ChattingListSideEffect.kt | 5 ++ .../android/feature/chat/ChattingListState.kt | 5 ++ .../feature/chat/ChattingListViewModel.kt | 14 +++++ .../feature/chat/navigation/ChatKey.kt | 7 +++ .../android/feature/chat/ExampleUnitTest.kt | 17 +++++ settings.gradle.kts | 2 + 22 files changed, 383 insertions(+) create mode 100644 feature/calendar/build.gradle.kts create mode 100644 feature/calendar/src/androidTest/java/com/gamss/android/feature/calendar/ExampleInstrumentedTest.kt create mode 100644 feature/calendar/src/main/AndroidManifest.xml create mode 100644 feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt create mode 100644 feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt create mode 100644 feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt create mode 100644 feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt create mode 100644 feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt create mode 100644 feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt create mode 100644 feature/chat/build.gradle.kts create mode 100644 feature/chat/src/androidTest/java/com/gamss/android/feature/chat/ExampleInstrumentedTest.kt create mode 100644 feature/chat/src/main/AndroidManifest.xml create mode 100644 feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt create mode 100644 feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt create mode 100644 feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt create mode 100644 feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt create mode 100644 feature/chat/src/main/java/com/gamss/android/feature/chat/navigation/ChatKey.kt create mode 100644 feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 531cb91..11e7cec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,6 +51,8 @@ dependencies { implementation(projects.core.common) implementation(projects.core.ui) implementation(projects.feature.home) + implementation(projects.feature.chat) + implementation(projects.feature.calendar) implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt index 89a9c2d..138a94a 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt @@ -8,6 +8,10 @@ import androidx.compose.ui.Modifier import androidx.navigation3.runtime.entryProvider import androidx.navigation3.ui.NavDisplay import com.gamss.android.core.ui.GamssBottomBar +import com.gamss.android.feature.calendar.CalendarScreen +import com.gamss.android.feature.calendar.navigation.CalendarKey +import com.gamss.android.feature.chat.ChattingListScreen +import com.gamss.android.feature.chat.navigation.ChatKey import com.gamss.android.feature.home.HomeScreen import com.gamss.android.feature.home.navigation.HomeKey @@ -33,6 +37,8 @@ fun GamssNavHost() { entries = navigationState.toEntries( entryProvider = entryProvider { entry { HomeScreen() } + entry { ChattingListScreen() } + entry { CalendarScreen() } }, ), onBack = { diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt index 429cb45..03c4574 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/TopLevelDestination.kt @@ -1,10 +1,14 @@ package com.gamss.android.app.navigation import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DateRange +import androidx.compose.material.icons.filled.Email import androidx.compose.material.icons.filled.Home import androidx.compose.ui.graphics.vector.ImageVector import androidx.navigation3.runtime.NavKey import com.gamss.android.core.ui.GamssBottomBarItem +import com.gamss.android.feature.calendar.navigation.CalendarKey +import com.gamss.android.feature.chat.navigation.ChatKey import com.gamss.android.feature.home.navigation.HomeKey /** @@ -20,6 +24,8 @@ data class TopLevelDestination( val topLevelDestinations = listOf( TopLevelDestination(key = HomeKey, icon = Icons.Filled.Home, label = "홈"), + TopLevelDestination(key = ChatKey, icon = Icons.Filled.Email, label = "대화"), + TopLevelDestination(key = CalendarKey, icon = Icons.Filled.DateRange, label = "달력"), ) val topLevelDestinationKeys = topLevelDestinations.map { it.key }.toSet() diff --git a/feature/calendar/build.gradle.kts b/feature/calendar/build.gradle.kts new file mode 100644 index 0000000..e19472b --- /dev/null +++ b/feature/calendar/build.gradle.kts @@ -0,0 +1,62 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.ksp) +} + +android { + namespace = "com.gamss.android.feature.calendar" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } +} + +dependencies { + implementation(projects.core.ui) + implementation(libs.androidx.core.ktx) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + + implementation(libs.navigation3.runtime) + implementation(libs.kotlinx.serialization.json) + + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + implementation(libs.orbit.core) + implementation(libs.orbit.viewmodel) + implementation(libs.orbit.compose) + + debugImplementation(libs.compose.ui.tooling) + + testImplementation(libs.junit) + testImplementation(libs.orbit.test) + androidTestImplementation(libs.androidx.junit) +} \ No newline at end of file diff --git a/feature/calendar/src/androidTest/java/com/gamss/android/feature/calendar/ExampleInstrumentedTest.kt b/feature/calendar/src/androidTest/java/com/gamss/android/feature/calendar/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..5e78716 --- /dev/null +++ b/feature/calendar/src/androidTest/java/com/gamss/android/feature/calendar/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.gamss.android.feature.calendar + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.gamss.android.feature.calendar.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/calendar/src/main/AndroidManifest.xml b/feature/calendar/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/feature/calendar/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt new file mode 100644 index 0000000..a77679a --- /dev/null +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt @@ -0,0 +1,34 @@ +package com.gamss.android.feature.calendar + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import org.orbitmvi.orbit.compose.collectSideEffect + +@Composable +fun CalendarScreen( + viewModel: CalendarViewModel = hiltViewModel(), +) { + viewModel.collectSideEffect { } + + Scaffold { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.Center, + ) { + Text( + text = "달력", + style = MaterialTheme.typography.headlineLarge, + ) + } + } +} \ No newline at end of file diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt new file mode 100644 index 0000000..e7579c8 --- /dev/null +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt @@ -0,0 +1,3 @@ +package com.gamss.android.feature.calendar + +sealed interface CalendarSideEffect \ No newline at end of file diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt new file mode 100644 index 0000000..fd6382c --- /dev/null +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt @@ -0,0 +1,5 @@ +package com.gamss.android.feature.calendar + +data class CalendarState( + val isLoading: Boolean = false, +) \ No newline at end of file diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt new file mode 100644 index 0000000..867ab26 --- /dev/null +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt @@ -0,0 +1,14 @@ +package com.gamss.android.feature.calendar + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + +@HiltViewModel +class CalendarViewModel @Inject constructor() : ViewModel(), + ContainerHost { + + override val container = container(CalendarState()) +} \ No newline at end of file diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt new file mode 100644 index 0000000..bc12533 --- /dev/null +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt @@ -0,0 +1,7 @@ +package com.gamss.android.feature.calendar.navigation + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +data object CalendarKey : NavKey \ No newline at end of file diff --git a/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt b/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt new file mode 100644 index 0000000..e5015a0 --- /dev/null +++ b/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.gamss.android.feature.calendar + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/feature/chat/build.gradle.kts b/feature/chat/build.gradle.kts new file mode 100644 index 0000000..917fd2d --- /dev/null +++ b/feature/chat/build.gradle.kts @@ -0,0 +1,61 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.ksp) +} + +android { + namespace = "com.gamss.android.feature.chat" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + + implementation(libs.navigation3.runtime) + implementation(libs.kotlinx.serialization.json) + + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + implementation(libs.orbit.core) + implementation(libs.orbit.viewmodel) + implementation(libs.orbit.compose) + + debugImplementation(libs.compose.ui.tooling) + + testImplementation(libs.junit) + testImplementation(libs.orbit.test) + androidTestImplementation(libs.androidx.junit) +} \ No newline at end of file diff --git a/feature/chat/src/androidTest/java/com/gamss/android/feature/chat/ExampleInstrumentedTest.kt b/feature/chat/src/androidTest/java/com/gamss/android/feature/chat/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b164cd6 --- /dev/null +++ b/feature/chat/src/androidTest/java/com/gamss/android/feature/chat/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.gamss.android.feature.chat + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.gamss.android.feature.chat.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/chat/src/main/AndroidManifest.xml b/feature/chat/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/feature/chat/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt new file mode 100644 index 0000000..42ccf6a --- /dev/null +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt @@ -0,0 +1,60 @@ +package com.gamss.android.feature.chat + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect + +private data class DummyChat( + val name: String, + val lastMessage: String, +) + +private val dummyChats = List(10) { + DummyChat(name = "채팅방 ${it + 1}", lastMessage = "임시 메시지 내용입니다.") +} + +@Composable +fun ChattingListScreen( + viewModel: ChattingListViewModel = hiltViewModel(), +) { + val state by viewModel.collectAsState() + + viewModel.collectSideEffect { } + + Scaffold( + topBar = { + Text( + text = "채팅", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(16.dp), + ) + }, + ) { innerPadding -> + if (state.isLoading) { + CircularProgressIndicator() + } else { + LazyColumn(modifier = Modifier.padding(innerPadding)) { + items(dummyChats) { chat -> + ListItem( + headlineContent = { Text(chat.name) }, + supportingContent = { Text(chat.lastMessage) }, + ) + Divider() + } + } + } + } +} \ No newline at end of file diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt new file mode 100644 index 0000000..06fa353 --- /dev/null +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt @@ -0,0 +1,5 @@ +package com.gamss.android.feature.chat + +sealed interface ChattingListSideEffect { + +} \ No newline at end of file diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt new file mode 100644 index 0000000..7854373 --- /dev/null +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt @@ -0,0 +1,5 @@ +package com.gamss.android.feature.chat + +data class ChattingListState ( + val isLoading: Boolean = false, +) \ No newline at end of file diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt new file mode 100644 index 0000000..ee5ced2 --- /dev/null +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt @@ -0,0 +1,14 @@ +package com.gamss.android.feature.chat + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + +@HiltViewModel +class ChattingListViewModel @Inject constructor() : ViewModel(), + ContainerHost { + + override val container = container(ChattingListState()) +} \ No newline at end of file diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/navigation/ChatKey.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/navigation/ChatKey.kt new file mode 100644 index 0000000..518747d --- /dev/null +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/navigation/ChatKey.kt @@ -0,0 +1,7 @@ +package com.gamss.android.feature.chat.navigation + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +data object ChatKey : NavKey diff --git a/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt b/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt new file mode 100644 index 0000000..dac8e85 --- /dev/null +++ b/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.gamss.android.feature.chat + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0cf2113..1174dbc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,3 +26,5 @@ include(":data") include(":core:common") include(":core:ui") include(":feature:home") +include(":feature:chat") +include(":feature:calendar") From 794f28da30e4708bc01b8d18d3d1fb17c7b6b7d5 Mon Sep 17 00:00:00 2001 From: seunghee Date: Tue, 21 Jul 2026 06:20:26 +0900 Subject: [PATCH 31/34] =?UTF-8?q?refactor:=20core:common=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20kotlinJvm=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/common/build.gradle.kts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 3dec3f8..bea50c5 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,24 +1,11 @@ plugins { - alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinJvm) } -android { - namespace = "com.gamss.android.core.common" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - +kotlin { + jvmToolchain(17) } dependencies { - implementation(libs.androidx.core.ktx) testImplementation(libs.junit) } \ No newline at end of file From 10c119cf0ca929c55a03f20bbd96b6093273c4a9 Mon Sep 17 00:00:00 2001 From: seunghee Date: Tue, 21 Jul 2026 06:22:35 +0900 Subject: [PATCH 32/34] =?UTF-8?q?fix:=20AuthRepository=20login=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=A5=BC=20AppResult=EB=A1=9C=20=EA=B0=90=EC=8B=B8?= =?UTF-8?q?=EA=B8=B0=20-=20AuthRepository.login()=EC=9D=B4=20Result=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20AppResult=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/data/repository/AuthRepositoryImpl.kt | 5 +++-- domain/build.gradle.kts | 2 ++ .../com/gamss/android/domain/repository/AuthRepository.kt | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt index a8b11eb..81edc11 100644 --- a/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/gamss/android/data/repository/AuthRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.gamss.android.data.repository +import com.gamss.android.core.common.AppResult import com.gamss.android.data.remote.auth.AuthService import com.gamss.android.data.remote.auth.model.request.LoginRequest import com.gamss.android.domain.model.AuthResponse @@ -10,6 +11,6 @@ class AuthRepositoryImpl @Inject constructor( private val authService: AuthService, ) : AuthRepository { - override suspend fun login(userId: String): AuthResponse = - authService.login(LoginRequest(userId = userId)).toDomain() + override suspend fun login(userId: String): AppResult = + AppResult.of { authService.login(LoginRequest(userId = userId)).toDomain() } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index a3b9cb9..c2a6b24 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -7,5 +7,7 @@ kotlin { } dependencies { + implementation(projects.core.common) + testImplementation(libs.junit) } diff --git a/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt index ea85677..9008a08 100644 --- a/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt +++ b/domain/src/main/kotlin/com/gamss/android/domain/repository/AuthRepository.kt @@ -1,7 +1,8 @@ package com.gamss.android.domain.repository +import com.gamss.android.core.common.AppResult import com.gamss.android.domain.model.AuthResponse interface AuthRepository { - suspend fun login(userId: String): AuthResponse + suspend fun login(userId: String): AppResult } From dbaaaa5438d767606a5dcf3c148c8debc106fa8c Mon Sep 17 00:00:00 2001 From: seunghee Date: Tue, 21 Jul 2026 06:25:09 +0900 Subject: [PATCH 33/34] =?UTF-8?q?feat:=20top-level=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=A7=8C=20BottomBar=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/app/navigation/GamssNavHost.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt index 138a94a..fa5165c 100644 --- a/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt +++ b/app/src/main/kotlin/com/gamss/android/app/navigation/GamssNavHost.kt @@ -25,11 +25,13 @@ fun GamssNavHost() { Scaffold( bottomBar = { - GamssBottomBar( - items = topLevelBottomBarItems, - selectedValue = navigationState.currentTopLevelKey, - onItemClick = navigator::navigate, - ) + if (navigationState.currentKey == navigationState.currentTopLevelKey) { + GamssBottomBar( + items = topLevelBottomBarItems, + selectedValue = navigationState.currentTopLevelKey, + onItemClick = navigator::navigate, + ) + } }, ) { innerPadding -> NavDisplay( From ce3e4cd7f65eca74ff0d29b6143e9c3c8ce5458c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=EC=8A=B9=ED=9D=AC?= Date: Tue, 21 Jul 2026 08:37:20 +0900 Subject: [PATCH 34/34] =?UTF-8?q?fix:=20feature:calendar,=20feature:chat?= =?UTF-8?q?=20=EB=AA=A8=EB=93=88=20detekt=20=EC=9C=84=EB=B0=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/gamss/android/feature/calendar/CalendarScreen.kt | 2 +- .../com/gamss/android/feature/calendar/CalendarSideEffect.kt | 2 +- .../java/com/gamss/android/feature/calendar/CalendarState.kt | 2 +- .../com/gamss/android/feature/calendar/CalendarViewModel.kt | 5 +++-- .../gamss/android/feature/calendar/navigation/CalendarKey.kt | 2 +- .../com/gamss/android/feature/calendar/ExampleUnitTest.kt | 5 ++--- .../com/gamss/android/feature/chat/ChattingListScreen.kt | 2 +- .../com/gamss/android/feature/chat/ChattingListSideEffect.kt | 4 +--- .../java/com/gamss/android/feature/chat/ChattingListState.kt | 4 ++-- .../com/gamss/android/feature/chat/ChattingListViewModel.kt | 5 +++-- .../java/com/gamss/android/feature/chat/ExampleUnitTest.kt | 5 ++--- 11 files changed, 18 insertions(+), 20 deletions(-) diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt index a77679a..b4cc2ae 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarScreen.kt @@ -31,4 +31,4 @@ fun CalendarScreen( ) } } -} \ No newline at end of file +} diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt index e7579c8..104161b 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarSideEffect.kt @@ -1,3 +1,3 @@ package com.gamss.android.feature.calendar -sealed interface CalendarSideEffect \ No newline at end of file +sealed interface CalendarSideEffect diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt index fd6382c..5ef4ebb 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarState.kt @@ -2,4 +2,4 @@ package com.gamss.android.feature.calendar data class CalendarState( val isLoading: Boolean = false, -) \ No newline at end of file +) diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt index 867ab26..8516751 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/CalendarViewModel.kt @@ -7,8 +7,9 @@ import org.orbitmvi.orbit.viewmodel.container import javax.inject.Inject @HiltViewModel -class CalendarViewModel @Inject constructor() : ViewModel(), +class CalendarViewModel @Inject constructor() : + ViewModel(), ContainerHost { override val container = container(CalendarState()) -} \ No newline at end of file +} diff --git a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt index bc12533..d21aa69 100644 --- a/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt +++ b/feature/calendar/src/main/java/com/gamss/android/feature/calendar/navigation/CalendarKey.kt @@ -4,4 +4,4 @@ import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable @Serializable -data object CalendarKey : NavKey \ No newline at end of file +data object CalendarKey : NavKey diff --git a/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt b/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt index e5015a0..27615f5 100644 --- a/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt +++ b/feature/calendar/src/test/java/com/gamss/android/feature/calendar/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.gamss.android.feature.calendar +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt index 42ccf6a..b86f56e 100644 --- a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListScreen.kt @@ -57,4 +57,4 @@ fun ChattingListScreen( } } } -} \ No newline at end of file +} diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt index 06fa353..5349ff9 100644 --- a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListSideEffect.kt @@ -1,5 +1,3 @@ package com.gamss.android.feature.chat -sealed interface ChattingListSideEffect { - -} \ No newline at end of file +sealed interface ChattingListSideEffect diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt index 7854373..087a0d5 100644 --- a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListState.kt @@ -1,5 +1,5 @@ package com.gamss.android.feature.chat -data class ChattingListState ( +data class ChattingListState( val isLoading: Boolean = false, -) \ No newline at end of file +) diff --git a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt index ee5ced2..fa0c732 100644 --- a/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt +++ b/feature/chat/src/main/java/com/gamss/android/feature/chat/ChattingListViewModel.kt @@ -7,8 +7,9 @@ import org.orbitmvi.orbit.viewmodel.container import javax.inject.Inject @HiltViewModel -class ChattingListViewModel @Inject constructor() : ViewModel(), +class ChattingListViewModel @Inject constructor() : + ViewModel(), ContainerHost { override val container = container(ChattingListState()) -} \ No newline at end of file +} diff --git a/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt b/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt index dac8e85..e765379 100644 --- a/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt +++ b/feature/chat/src/test/java/com/gamss/android/feature/chat/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.gamss.android.feature.chat +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +}