From 906531f037de4f4c374a5d8672776f5d06dcc8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Wed, 25 Jun 2025 23:37:28 +0900 Subject: [PATCH 1/7] build: Add koin dependency --- app/build.gradle.kts | 2 ++ auth/data/build.gradle.kts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c637bee..62e61c8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,6 +34,8 @@ dependencies { // Crypto implementation(libs.androidx.security.crypto.ktx) + implementation(libs.bundles.koin) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/auth/data/build.gradle.kts b/auth/data/build.gradle.kts index ee74840..ed49906 100644 --- a/auth/data/build.gradle.kts +++ b/auth/data/build.gradle.kts @@ -8,6 +8,8 @@ android { } dependencies { + implementation(libs.bundles.koin) + implementation(projects.auth.domain) implementation(projects.core.domain) implementation(projects.core.data) From e71f35b0570ba3b1592e1a4cab2bde9ded07363b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 14:03:15 +0900 Subject: [PATCH 2/7] feat: Add home module --- home/data/.gitignore | 1 + home/data/build.gradle.kts | 16 +++++++++++++ home/data/consumer-rules.pro | 0 home/data/proguard-rules.pro | 21 ++++++++++++++++ .../home/data/ExampleInstrumentedTest.kt | 24 +++++++++++++++++++ home/data/src/main/AndroidManifest.xml | 4 ++++ .../com/hyunjung/home/data/ExampleUnitTest.kt | 17 +++++++++++++ home/domain/.gitignore | 1 + home/domain/build.gradle.kts | 7 ++++++ .../java/com/hyunjung/home/domain/MyClass.kt | 4 ++++ home/presentation/.gitignore | 1 + home/presentation/build.gradle.kts | 12 ++++++++++ home/presentation/consumer-rules.pro | 0 home/presentation/proguard-rules.pro | 21 ++++++++++++++++ .../presentation/ExampleInstrumentedTest.kt | 24 +++++++++++++++++++ .../presentation/src/main/AndroidManifest.xml | 4 ++++ .../home/presentation/ExampleUnitTest.kt | 17 +++++++++++++ settings.gradle.kts | 5 +++- 18 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 home/data/.gitignore create mode 100644 home/data/build.gradle.kts create mode 100644 home/data/consumer-rules.pro create mode 100644 home/data/proguard-rules.pro create mode 100644 home/data/src/androidTest/java/com/hyunjung/home/data/ExampleInstrumentedTest.kt create mode 100644 home/data/src/main/AndroidManifest.xml create mode 100644 home/data/src/test/java/com/hyunjung/home/data/ExampleUnitTest.kt create mode 100644 home/domain/.gitignore create mode 100644 home/domain/build.gradle.kts create mode 100644 home/domain/src/main/java/com/hyunjung/home/domain/MyClass.kt create mode 100644 home/presentation/.gitignore create mode 100644 home/presentation/build.gradle.kts create mode 100644 home/presentation/consumer-rules.pro create mode 100644 home/presentation/proguard-rules.pro create mode 100644 home/presentation/src/androidTest/java/com/hyunjung/home/presentation/ExampleInstrumentedTest.kt create mode 100644 home/presentation/src/main/AndroidManifest.xml create mode 100644 home/presentation/src/test/java/com/hyunjung/home/presentation/ExampleUnitTest.kt diff --git a/home/data/.gitignore b/home/data/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/home/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/home/data/build.gradle.kts b/home/data/build.gradle.kts new file mode 100644 index 0000000..38daa65 --- /dev/null +++ b/home/data/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(libs.plugins.hyunjung.cherrydan.android.library) + alias(libs.plugins.hyunjung.cherrydan.jvm.ktor) +} + +android { + namespace = "com.hyunjung.home.data" +} + +dependencies { + implementation(libs.bundles.koin) + + implementation(projects.home.domain) + implementation(projects.core.domain) + implementation(projects.core.data) +} \ No newline at end of file diff --git a/home/data/consumer-rules.pro b/home/data/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/home/data/proguard-rules.pro b/home/data/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/home/data/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/home/data/src/androidTest/java/com/hyunjung/home/data/ExampleInstrumentedTest.kt b/home/data/src/androidTest/java/com/hyunjung/home/data/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2d761d6 --- /dev/null +++ b/home/data/src/androidTest/java/com/hyunjung/home/data/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.hyunjung.home.data + +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.hyunjung.home.data.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/home/data/src/main/AndroidManifest.xml b/home/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/home/data/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/home/data/src/test/java/com/hyunjung/home/data/ExampleUnitTest.kt b/home/data/src/test/java/com/hyunjung/home/data/ExampleUnitTest.kt new file mode 100644 index 0000000..da58299 --- /dev/null +++ b/home/data/src/test/java/com/hyunjung/home/data/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.hyunjung.home.data + +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/home/domain/.gitignore b/home/domain/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/home/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/home/domain/build.gradle.kts b/home/domain/build.gradle.kts new file mode 100644 index 0000000..d7c49a4 --- /dev/null +++ b/home/domain/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + alias(libs.plugins.hyunjung.cherrydan.jvm.library) +} + +dependencies { + implementation(projects.core.domain) +} \ No newline at end of file diff --git a/home/domain/src/main/java/com/hyunjung/home/domain/MyClass.kt b/home/domain/src/main/java/com/hyunjung/home/domain/MyClass.kt new file mode 100644 index 0000000..d6374a4 --- /dev/null +++ b/home/domain/src/main/java/com/hyunjung/home/domain/MyClass.kt @@ -0,0 +1,4 @@ +package com.hyunjung.home.domain + +class MyClass { +} \ No newline at end of file diff --git a/home/presentation/.gitignore b/home/presentation/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/home/presentation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/home/presentation/build.gradle.kts b/home/presentation/build.gradle.kts new file mode 100644 index 0000000..0593448 --- /dev/null +++ b/home/presentation/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + alias(libs.plugins.hyunjung.cherrydan.android.feature.ui) +} + +android { + namespace = "com.hyunjung.home.presentation" +} + +dependencies { + implementation(projects.core.domain) + implementation(projects.home.domain) +} \ No newline at end of file diff --git a/home/presentation/consumer-rules.pro b/home/presentation/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/home/presentation/proguard-rules.pro b/home/presentation/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/home/presentation/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/home/presentation/src/androidTest/java/com/hyunjung/home/presentation/ExampleInstrumentedTest.kt b/home/presentation/src/androidTest/java/com/hyunjung/home/presentation/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..9f34103 --- /dev/null +++ b/home/presentation/src/androidTest/java/com/hyunjung/home/presentation/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.hyunjung.home.presentation + +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.hyunjung.home.presentation.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/home/presentation/src/main/AndroidManifest.xml b/home/presentation/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/home/presentation/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/home/presentation/src/test/java/com/hyunjung/home/presentation/ExampleUnitTest.kt b/home/presentation/src/test/java/com/hyunjung/home/presentation/ExampleUnitTest.kt new file mode 100644 index 0000000..8b38fd0 --- /dev/null +++ b/home/presentation/src/test/java/com/hyunjung/home/presentation/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.hyunjung.home.presentation + +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 f8ce2e2..553f5de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,4 +32,7 @@ include(":core:presentation:ui") include(":core:domain") include(":core:data") include(":core:database") -include(":core:network") \ No newline at end of file +include(":core:network") +include(":home:data") +include(":home:domain") +include(":home:presentation") \ No newline at end of file From c43869115237c422a2754fbbd9e142fa1ddb243c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 14:09:57 +0900 Subject: [PATCH 3/7] chore: Add navigation bar icons --- .../core/presentation/designsystem/Icons.kt | 42 ++++++++++++++++++- .../src/main/res/drawable/ic_nav_category.xml | 33 +++++++++++++++ .../drawable/ic_nav_category_unselected.xml | 33 +++++++++++++++ .../src/main/res/drawable/ic_nav_home.xml | 22 ++++++++++ .../res/drawable/ic_nav_home_unselected.xml | 20 +++++++++ .../main/res/drawable/ic_nav_my_campaigns.xml | 26 ++++++++++++ .../ic_nav_my_campaigns_unselected.xml | 26 ++++++++++++ .../src/main/res/drawable/ic_nav_my_page.xml | 23 ++++++++++ .../drawable/ic_nav_my_page_unselected.xml | 23 ++++++++++ .../src/main/res/drawable/ic_nav_notice.xml | 27 ++++++++++++ .../res/drawable/ic_nav_notice_unselected.xml | 27 ++++++++++++ .../ui/src/main/res/values/strings.xml | 8 ++++ 12 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_category.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_category_unselected.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_home.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_home_unselected.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns_unselected.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page_unselected.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_notice.xml create mode 100644 core/presentation/designsystem/src/main/res/drawable/ic_nav_notice_unselected.xml diff --git a/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/Icons.kt b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/Icons.kt index 7b6432a..9267faa 100644 --- a/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/Icons.kt +++ b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/Icons.kt @@ -46,4 +46,44 @@ val KakaoIcon: ImageVector val NaverIcon: ImageVector @Composable - get() = ImageVector.vectorResource(id = R.drawable.ic_naver) \ No newline at end of file + get() = ImageVector.vectorResource(id = R.drawable.ic_naver) + +val NavCategoryUnselectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_category_unselected) + +val NavNoticeUnselectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_notice_unselected) + +val NavHomeUnselectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_home_unselected) + +val NavMyCampaignsUnselectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_my_campaigns_unselected) + +val NavMyPageUnselectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_my_page_unselected) + +val NavCategorySelectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_category) + +val NavNoticeSelectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_notice) + +val NavHomeSelectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_home) + +val NavMyCampaignsSelectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_my_campaigns) + +val NavMyPageSelectedIcon: ImageVector + @Composable + get() = ImageVector.vectorResource(id = R.drawable.ic_nav_my_page) \ No newline at end of file diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_category.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_category.xml new file mode 100644 index 0000000..85fcc09 --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_category.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_category_unselected.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_category_unselected.xml new file mode 100644 index 0000000..ca365fc --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_category_unselected.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_home.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_home.xml new file mode 100644 index 0000000..d94ce5d --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_home.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_home_unselected.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_home_unselected.xml new file mode 100644 index 0000000..da7ae76 --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_home_unselected.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns.xml new file mode 100644 index 0000000..574a5a3 --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns_unselected.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns_unselected.xml new file mode 100644 index 0000000..886053f --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_campaigns_unselected.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page.xml new file mode 100644 index 0000000..f5ef1cd --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page_unselected.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page_unselected.xml new file mode 100644 index 0000000..b59595a --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_my_page_unselected.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice.xml new file mode 100644 index 0000000..9847099 --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice_unselected.xml b/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice_unselected.xml new file mode 100644 index 0000000..c04b001 --- /dev/null +++ b/core/presentation/designsystem/src/main/res/drawable/ic_nav_notice_unselected.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/core/presentation/ui/src/main/res/values/strings.xml b/core/presentation/ui/src/main/res/values/strings.xml index 6b2e670..4d99583 100644 --- a/core/presentation/ui/src/main/res/values/strings.xml +++ b/core/presentation/ui/src/main/res/values/strings.xml @@ -18,4 +18,12 @@ 카카오로 로그인 네이버로 로그인 Google로 로그인 + + + 체리단 + 카테고리 + 체리단 소식 + + 내 체험단 + 마이페이지 \ No newline at end of file From 96abb8ad8ac86abde1c32e48973ce4e6f5c02dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 16:43:56 +0900 Subject: [PATCH 4/7] feat: Add BorderExtensions --- .../designsystem/util/BorderExtensions.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/util/BorderExtensions.kt diff --git a/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/util/BorderExtensions.kt b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/util/BorderExtensions.kt new file mode 100644 index 0000000..0a73ef7 --- /dev/null +++ b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/util/BorderExtensions.kt @@ -0,0 +1,58 @@ +package com.hyunjung.core.presentation.designsystem.util + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color + +fun Modifier.topBorder( + color: Color, + height: Float, +) = this.drawWithContent { + drawContent() + drawLine( + color = color, + start = Offset(0f, 0f), + end = Offset(size.width, 0f), + strokeWidth = height, + ) +} + +fun Modifier.rightBorder( + color: Color, + width: Float, +) = this.drawWithContent { + drawContent() + drawLine( + color = color, + start = Offset(size.width, 0f), + end = Offset(size.width, size.height), + strokeWidth = width, + ) +} + +fun Modifier.bottomBorder( + color: Color, + height: Float, +) = this.drawWithContent { + drawContent() + drawLine( + color = color, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = height, + ) +} + +fun Modifier.leftBorder( + color: Color, + width: Float, +) = this.drawWithContent { + drawContent() + drawLine( + color = color, + start = Offset(0f, 0f), + end = Offset(0f, size.height), + strokeWidth = width, + ) +} \ No newline at end of file From 731fa1129eac218082911ddb7bdaa29e1a94f646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 16:59:04 +0900 Subject: [PATCH 5/7] feat: Add navigation screens --- .../presentation/category/CategoryScreen.kt | 16 +++++++++++ .../home/presentation/home/HomeScreen.kt | 28 +++++++++++++++++++ .../my_campaigns/MyCampaignsScreen.kt | 16 +++++++++++ .../home/presentation/my_page/MyPageScreen.kt | 16 +++++++++++ 4 files changed, 76 insertions(+) create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/category/CategoryScreen.kt create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/home/HomeScreen.kt create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/my_campaigns/MyCampaignsScreen.kt create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/my_page/MyPageScreen.kt diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/category/CategoryScreen.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/category/CategoryScreen.kt new file mode 100644 index 0000000..03abf6f --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/category/CategoryScreen.kt @@ -0,0 +1,16 @@ +package com.hyunjung.home.presentation.category + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTypography + +@Composable +fun CategoryScreen(modifier: Modifier = Modifier) { + Text( + text = "카테고리", + style = CherrydanTypography.Title1, + color = CherrydanColors.Black + ) +} \ No newline at end of file diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/home/HomeScreen.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/home/HomeScreen.kt new file mode 100644 index 0000000..d3f3c2f --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/home/HomeScreen.kt @@ -0,0 +1,28 @@ +package com.hyunjung.home.presentation.home + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTypography + +@Composable +fun HomeScreenRoot(modifier: Modifier = Modifier) { + HomeScreen(modifier = modifier) +} + +@Composable +fun HomeScreen(modifier: Modifier = Modifier) { + Text( + text = "홈 화면", + style = CherrydanTypography.Title1, + color = CherrydanColors.Black + ) +} + +@Preview +@Composable +private fun HomeScreenPreview() { + HomeScreen() +} \ No newline at end of file diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/my_campaigns/MyCampaignsScreen.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/my_campaigns/MyCampaignsScreen.kt new file mode 100644 index 0000000..5632ee6 --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/my_campaigns/MyCampaignsScreen.kt @@ -0,0 +1,16 @@ +package com.hyunjung.home.presentation.my_campaigns + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTypography + +@Composable +fun MyCampaignsScreen(modifier: Modifier = Modifier) { + Text( + text = "내 체험단 화면", + style = CherrydanTypography.Title1, + color = CherrydanColors.Black + ) +} \ No newline at end of file diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/my_page/MyPageScreen.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/my_page/MyPageScreen.kt new file mode 100644 index 0000000..391de7f --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/my_page/MyPageScreen.kt @@ -0,0 +1,16 @@ +package com.hyunjung.home.presentation.my_page + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTypography + +@Composable +fun MyPageScreen(modifier: Modifier = Modifier) { + Text( + text = "마이페이지", + style = CherrydanTypography.Title1, + color = CherrydanColors.Black + ) +} \ No newline at end of file From 2377bdab9ca56160acbbacf7c639d3c1566afe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 16:59:38 +0900 Subject: [PATCH 6/7] feat: Add NoticeScreen --- .../home/presentation/notice/NoticeScreen.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/notice/NoticeScreen.kt diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/notice/NoticeScreen.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/notice/NoticeScreen.kt new file mode 100644 index 0000000..d2c4ae9 --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/notice/NoticeScreen.kt @@ -0,0 +1,16 @@ +package com.hyunjung.home.presentation.notice + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTypography + +@Composable +fun NoticeScreen(modifier: Modifier = Modifier) { + Text( + text = "체리단 소식", + style = CherrydanTypography.Title1, + color = CherrydanColors.Black + ) +} \ No newline at end of file From c3061a7c6cd1a8cf83752cb71fe6487d5e4ded2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=ED=98=84=EC=A0=95?= Date: Thu, 26 Jun 2025 17:02:34 +0900 Subject: [PATCH 7/7] feat: Implement bottom navigation --- app/build.gradle.kts | 4 + .../com/hyunjung/cherrydan/MainActivity.kt | 38 +-- .../component/CherrydanBottomNavigation.kt | 241 ++++++++++++++++++ home/presentation/build.gradle.kts | 2 + .../presentation/navigation/HomeNavigation.kt | 150 +++++++++++ 5 files changed, 402 insertions(+), 33 deletions(-) create mode 100644 core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/component/CherrydanBottomNavigation.kt create mode 100644 home/presentation/src/main/java/com/hyunjung/home/presentation/navigation/HomeNavigation.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 62e61c8..43ee916 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,4 +66,8 @@ dependencies { implementation(projects.auth.presentation) implementation(projects.auth.domain) implementation(projects.auth.data) + + implementation(projects.home.presentation) + implementation(projects.home.domain) + implementation(projects.home.data) } \ No newline at end of file diff --git a/app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt b/app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt index 61e7ecc..57a6e68 100644 --- a/app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt +++ b/app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt @@ -4,18 +4,10 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import com.hyunjung.core.presentation.designsystem.CalendarIcon -import com.hyunjung.core.presentation.designsystem.CherrydanColors import com.hyunjung.core.presentation.designsystem.CherrydanTheme -import com.hyunjung.core.presentation.designsystem.CherrydanTypography +import com.hyunjung.home.presentation.navigation.HomeNavigation class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -23,36 +15,16 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { CherrydanTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding) - ) - } + HomeNavigation() } } } } +@Preview @Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier, - color = CherrydanColors.MainPink3, - style = CherrydanTypography.Title1 - ) - Icon( - imageVector = CalendarIcon, - modifier = modifier, - contentDescription = null - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { +private fun MainActivityPreview() { CherrydanTheme { - Greeting("Android") + HomeNavigation() } } \ No newline at end of file diff --git a/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/component/CherrydanBottomNavigation.kt b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/component/CherrydanBottomNavigation.kt new file mode 100644 index 0000000..d9210b1 --- /dev/null +++ b/core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/component/CherrydanBottomNavigation.kt @@ -0,0 +1,241 @@ +package com.hyunjung.core.presentation.designsystem.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hyunjung.core.presentation.designsystem.CherrydanColors +import com.hyunjung.core.presentation.designsystem.CherrydanTheme +import com.hyunjung.core.presentation.designsystem.CherrydanTypography +import com.hyunjung.core.presentation.designsystem.NavCategorySelectedIcon +import com.hyunjung.core.presentation.designsystem.NavCategoryUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavHomeSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavHomeUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyCampaignsSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyCampaignsUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyPageSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyPageUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavNoticeSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavNoticeUnselectedIcon +import com.hyunjung.core.presentation.designsystem.util.topBorder + +/** + * Bottom Navigation Item 데이터 클래스 + * + * @param title 네비게이션 아이템의 제목 + * @param unselectedIcon 선택되지 않았을 때의 아이콘 + * @param selectedIcon 선택되었을 때의 아이콘 + */ +data class BottomNavItem( + val title: String, + val unselectedIcon: @Composable () -> ImageVector, + val selectedIcon: @Composable () -> ImageVector +) + +/** + * CherryDan 앱의 Bottom Navigation 컴포넌트 + * + * @param items 네비게이션 아이템 리스트 + * @param selectedIndex 현재 선택된 아이템의 인덱스 (0부터 시작) + * @param onItemSelected 아이템이 선택되었을 때 호출되는 콜백 함수 + * @param modifier 추가적인 Modifier + */ +@Composable +fun CherrydanBottomNavigation( + items: List, + selectedIndex: Int, + onItemSelected: (Int) -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier + .fillMaxWidth() + .topBorder( + color = CherrydanColors.PointBeige, + height = 2f + ), + color = CherrydanColors.White, + tonalElevation = 12.dp + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + items.forEachIndexed { index, item -> + CherrydanBottomNavItem( + item = item, + isSelected = selectedIndex == index, + onClick = { onItemSelected(index) }, + modifier = Modifier.weight(1f) + ) + } + } + } +} + +/** + * 개별 Bottom Navigation Item 컴포넌트 + * + * @param item 네비게이션 아이템 데이터 + * @param isSelected 현재 선택된 상태인지 여부 + * @param onClick 클릭 시 호출되는 콜백 함수 + * @param modifier 추가적인 Modifier + */ +@Composable +private fun CherrydanBottomNavItem( + item: BottomNavItem, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val animatedColor = if (isSelected) { + CherrydanColors.MainPink3 + } else { + CherrydanColors.MainPink1 + } + + Column( + modifier = modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onClick() } + .padding(top = 12.dp, bottom = 4.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = if (isSelected) item.selectedIcon() else item.unselectedIcon(), + tint = Color.Unspecified, + contentDescription = item.title, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = item.title, + style = CherrydanTypography.Main6_R, + color = animatedColor, + textAlign = TextAlign.Center, + maxLines = 1 + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun CherrydanBottomNavigationPreview() { + val sampleItems = listOf( + BottomNavItem( + title = "카테고리", + unselectedIcon = { NavCategoryUnselectedIcon }, + selectedIcon = { NavCategorySelectedIcon } + ), + BottomNavItem( + title = "체리단 소식", + unselectedIcon = { NavNoticeUnselectedIcon }, + selectedIcon = { NavNoticeSelectedIcon } + ), + BottomNavItem( + title = "홈", + unselectedIcon = { NavHomeUnselectedIcon }, + selectedIcon = { NavHomeSelectedIcon } + ), + BottomNavItem( + title = "내 체험단", + unselectedIcon = { NavMyCampaignsUnselectedIcon }, + selectedIcon = { NavMyCampaignsSelectedIcon } + ), + BottomNavItem( + title = "마이페이지", + unselectedIcon = { NavMyPageUnselectedIcon }, + selectedIcon = { NavMyPageSelectedIcon } + ) + ) + + CherrydanTheme { + CherrydanBottomNavigation( + items = sampleItems, + selectedIndex = 2, // 홈이 선택된 상태 + onItemSelected = { /* Preview에서는 동작 안함 */ } + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun CherrydanBottomNavigationUsageExample() { + CherrydanTheme { + Column { + // 메인 콘텐츠 영역 (예시) + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text("메인 콘텐츠 영역") + } + + // Bottom Navigation + CherrydanBottomNavigation( + items = listOf( + BottomNavItem( + title = "카테고리", + unselectedIcon = { NavCategoryUnselectedIcon }, + selectedIcon = { NavCategorySelectedIcon } + ), + BottomNavItem( + title = "체리단 소식", + unselectedIcon = { NavNoticeUnselectedIcon }, + selectedIcon = { NavNoticeSelectedIcon } + ), + BottomNavItem( + title = "홈", + unselectedIcon = { NavHomeUnselectedIcon }, + selectedIcon = { NavHomeSelectedIcon } + ), + BottomNavItem( + title = "내 체험단", + unselectedIcon = { NavMyCampaignsUnselectedIcon }, + selectedIcon = { NavMyCampaignsSelectedIcon } + ), + BottomNavItem( + title = "마이페이지", + unselectedIcon = { NavMyPageUnselectedIcon }, + selectedIcon = { NavMyPageSelectedIcon } + ) + ), + selectedIndex = 2, + onItemSelected = { index -> + // 실제 앱에서는 여기에 네비게이션 로직이 들어감 + println("Selected tab: $index") + } + ) + } + } +} \ No newline at end of file diff --git a/home/presentation/build.gradle.kts b/home/presentation/build.gradle.kts index 0593448..5bdeb17 100644 --- a/home/presentation/build.gradle.kts +++ b/home/presentation/build.gradle.kts @@ -7,6 +7,8 @@ android { } dependencies { + implementation(libs.androidx.navigation.compose) + implementation(projects.core.domain) implementation(projects.home.domain) } \ No newline at end of file diff --git a/home/presentation/src/main/java/com/hyunjung/home/presentation/navigation/HomeNavigation.kt b/home/presentation/src/main/java/com/hyunjung/home/presentation/navigation/HomeNavigation.kt new file mode 100644 index 0000000..6eaa2e0 --- /dev/null +++ b/home/presentation/src/main/java/com/hyunjung/home/presentation/navigation/HomeNavigation.kt @@ -0,0 +1,150 @@ +package com.hyunjung.home.presentation.navigation + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.hyunjung.core.presentation.designsystem.NavCategorySelectedIcon +import com.hyunjung.core.presentation.designsystem.NavCategoryUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavHomeSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavHomeUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyCampaignsSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyCampaignsUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyPageSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavMyPageUnselectedIcon +import com.hyunjung.core.presentation.designsystem.NavNoticeSelectedIcon +import com.hyunjung.core.presentation.designsystem.NavNoticeUnselectedIcon +import com.hyunjung.core.presentation.designsystem.component.BottomNavItem +import com.hyunjung.core.presentation.designsystem.component.CherrydanBottomNavigation +import com.hyunjung.core.presentation.ui.R +import com.hyunjung.home.presentation.category.CategoryScreen +import com.hyunjung.home.presentation.home.HomeScreen +import com.hyunjung.home.presentation.my_campaigns.MyCampaignsScreen +import com.hyunjung.home.presentation.my_page.MyPageScreen +import com.hyunjung.home.presentation.notice.NoticeScreen + +object HomeNavRoute { + const val CATEGORY = "category" + const val NOTICE = "notice" + const val HOME = "home" + const val MY_CAMPAIGNS = "my_campaigns" + const val MY_PAGE = "my_page" +} + +@Composable +fun HomeNavigation() { + val navController = rememberNavController() + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + + val selectedIndex = when (currentRoute) { + HomeNavRoute.CATEGORY -> 0 + HomeNavRoute.NOTICE -> 1 + HomeNavRoute.HOME -> 2 + HomeNavRoute.MY_CAMPAIGNS -> 3 + HomeNavRoute.MY_PAGE -> 4 + else -> 2 + } + + val bottomNavItems = listOf( + BottomNavItem( + title = stringResource(R.string.home_nav_category), + unselectedIcon = { NavCategoryUnselectedIcon }, + selectedIcon = { NavCategorySelectedIcon } + ), + BottomNavItem( + title = stringResource(R.string.home_nav_notice), + unselectedIcon = { NavNoticeUnselectedIcon }, + selectedIcon = { NavNoticeSelectedIcon } + ), + BottomNavItem( + title = stringResource(R.string.home_nav_home), + unselectedIcon = { NavHomeUnselectedIcon }, + selectedIcon = { NavHomeSelectedIcon } + ), + BottomNavItem( + title = stringResource(R.string.home_nav_my_campaigns), + unselectedIcon = { NavMyCampaignsUnselectedIcon }, + selectedIcon = { NavMyCampaignsSelectedIcon } + ), + BottomNavItem( + title = stringResource(R.string.home_nav_my_page), + unselectedIcon = { NavMyPageUnselectedIcon }, + selectedIcon = { NavMyPageSelectedIcon } + ) + ) + + Scaffold( + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.systemBars), + bottomBar = { + CherrydanBottomNavigation( + items = bottomNavItems, + selectedIndex = selectedIndex, + onItemSelected = { index -> + handleTabNavigation( + navController = navController, + currentRoute = currentRoute, + targetIndex = index + ) + } + ) + } + ) { paddingValues -> + NavHost( + navController = navController, + startDestination = HomeNavRoute.HOME, + modifier = Modifier.padding(paddingValues) + ) { + composable(HomeNavRoute.CATEGORY) { CategoryScreen() } + composable(HomeNavRoute.NOTICE) { NoticeScreen() } + composable(HomeNavRoute.HOME) { HomeScreen() } + composable(HomeNavRoute.MY_CAMPAIGNS) { MyCampaignsScreen() } + composable(HomeNavRoute.MY_PAGE) { MyPageScreen() } + } + } +} + +private fun handleTabNavigation( + navController: NavController, + currentRoute: String?, + targetIndex: Int +) { + val targetRoute = when (targetIndex) { + 0 -> HomeNavRoute.CATEGORY + 1 -> HomeNavRoute.NOTICE + 2 -> HomeNavRoute.HOME + 3 -> HomeNavRoute.MY_CAMPAIGNS + 4 -> HomeNavRoute.MY_PAGE + else -> HomeNavRoute.HOME + } + + // 이미 같은 탭이면 아무것도 하지 않음 (또는 스크롤을 맨 위로) + if (currentRoute == targetRoute) { + // TODO: 스크롤을 맨 위로 이동하는 로직 추가 가능 + return + } + + navController.navigate(targetRoute) { + // 백스택에서 시작 화면까지 모든 화면 제거하고 상태 저장 + popUpTo(navController.graph.startDestinationId) { + saveState = true + } + // 동일한 destination 중복 방지 + launchSingleTop = true + // 상태 복원 + restoreState = true + } +} \ No newline at end of file