From 292a8c5c1f179727444568fef8a81b45d955c336 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:32:20 +0900 Subject: [PATCH 01/22] add mypage feature module --- feature/mypage/.gitignore | 1 + feature/mypage/build.gradle.kts | 31 +++++++++++++++++++ feature/mypage/consumer-rules.pro | 0 feature/mypage/proguard-rules.pro | 21 +++++++++++++ .../kikidan/mypage/ExampleInstrumentedTest.kt | 24 ++++++++++++++ feature/mypage/src/main/AndroidManifest.xml | 4 +++ .../com/kikidan/mypage/ExampleUnitTest.kt | 17 ++++++++++ settings.gradle.kts | 1 + 8 files changed, 99 insertions(+) create mode 100644 feature/mypage/.gitignore create mode 100644 feature/mypage/build.gradle.kts create mode 100644 feature/mypage/consumer-rules.pro create mode 100644 feature/mypage/proguard-rules.pro create mode 100644 feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt create mode 100644 feature/mypage/src/main/AndroidManifest.xml create mode 100644 feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt diff --git a/feature/mypage/.gitignore b/feature/mypage/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/mypage/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts new file mode 100644 index 0000000..d3660b9 --- /dev/null +++ b/feature/mypage/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "com.kikidan.mypage" + compileSdk { + version = release(37) + } + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + +} + +dependencies { + implementation(libs.androidx.appcompat) + implementation(libs.androidx.core.ktx) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.junit) +} diff --git a/feature/mypage/consumer-rules.pro b/feature/mypage/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/feature/mypage/proguard-rules.pro b/feature/mypage/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/feature/mypage/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/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt b/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..babd854 --- /dev/null +++ b/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.kikidan.mypage + +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.kikidan.mypage.test", appContext.packageName) + } +} diff --git a/feature/mypage/src/main/AndroidManifest.xml b/feature/mypage/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8bdb7e1 --- /dev/null +++ b/feature/mypage/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt b/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt new file mode 100644 index 0000000..a62a916 --- /dev/null +++ b/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.kikidan.mypage + +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) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c4176d5..48a0d32 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,3 +36,4 @@ include(":core:data-remote") include(":core:navigation") include(":feature:login") include(":core:designsystem") +include(":feature:mypage") From 564c31c7b06ed9d84bf28978673398956119b41a Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:33:07 +0900 Subject: [PATCH 02/22] apply feature convention plugin to mypage module --- feature/mypage/build.gradle.kts | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index d3660b9..f0d4cbf 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -1,31 +1,7 @@ plugins { - alias(libs.plugins.android.library) + id("todakun.feature") } android { namespace = "com.kikidan.mypage" - compileSdk { - version = release(37) - } - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - -} - -dependencies { - implementation(libs.androidx.appcompat) - implementation(libs.androidx.core.ktx) - implementation(libs.material) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.junit) } From b9ff15baf7c4010ceed1f655a2f71541ffa8663c Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:13:53 +0900 Subject: [PATCH 03/22] =?UTF-8?q?[Feat]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=88=20ViewModel=20=EB=B0=8F=20UI=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add MyPageHomeViewModel using Orbit MVI * feat: define MyPageHomeUiState, MyPageHomeUiModel, and MyPageHomeSideEffect --- .../mypage/home/MyPageHomeViewModel.kt | 44 +++++++++++++++++++ .../mypage/home/model/MyPageHomeSideEffect.kt | 3 ++ .../mypage/home/model/MyPageHomeUiModel.kt | 11 +++++ .../mypage/home/model/MyPageHomeUiState.kt | 7 +++ 4 files changed, 65 insertions(+) create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeSideEffect.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt new file mode 100644 index 0000000..dd5d019 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt @@ -0,0 +1,44 @@ +package com.kikidan.mypage.home + +import androidx.lifecycle.ViewModel +import com.kikidan.domain.usecase.mypage.GetMyPageInfoUseCase +import com.kikidan.mypage.home.model.MyPageHomeSideEffect +import com.kikidan.mypage.home.model.MyPageHomeUiModel +import com.kikidan.mypage.home.model.MyPageHomeUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.Syntax +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + + + +private typealias MyPageHomeSyntax = Syntax + +@HiltViewModel +class MyPageHomeViewModel @Inject constructor( + private val getMyPageInfoUseCase: GetMyPageInfoUseCase, +) : ViewModel(), ContainerHost { + override val container: Container = container(MyPageHomeUiState.Loading) { + repeatOnSubscription { + loadMyPageInfo() + } + } + + fun loadMyPageInfo() = intent { + getMyPageInfoUseCase() + .onSuccess { info -> + reduce { MyPageHomeUiState.Success(MyPageHomeUiModel(info.user, info.sajuPalja)) } + } + .onFailure { throwable -> + reduce { MyPageHomeUiState.Fail(throwable) } + } + } + + private suspend fun MyPageHomeSyntax.reduceIfSuccess(reducer: MyPageHomeUiState.Success.() -> MyPageHomeUiState) { + reduce { + (state as? MyPageHomeUiState.Success)?.reducer() ?: state + } + } +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeSideEffect.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeSideEffect.kt new file mode 100644 index 0000000..66236d5 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeSideEffect.kt @@ -0,0 +1,3 @@ +package com.kikidan.mypage.home.model + +sealed interface MyPageHomeSideEffect diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt new file mode 100644 index 0000000..245c883 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt @@ -0,0 +1,11 @@ +package com.kikidan.mypage.home.model + +import com.kikidan.domain.model.saju.SajuPalja +import com.kikidan.domain.model.user.User + + + +data class MyPageHomeUiModel( + val user: User, + val sajuPalja: SajuPalja +) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt new file mode 100644 index 0000000..7a9d91d --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt @@ -0,0 +1,7 @@ +package com.kikidan.mypage.home.model + +sealed interface MyPageHomeUiState { + data object Loading : MyPageHomeUiState + data class Success(val model: MyPageHomeUiModel) : MyPageHomeUiState + data class Fail(val e: Throwable) : MyPageHomeUiState +} From e269132504ede387e0109ffad835d5f57fb4ea77 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:35:56 +0900 Subject: [PATCH 04/22] =?UTF-8?q?[Chore]=20MyPage=20feature=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=BD=94=EB=93=9C=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: feature:mypage 모듈 내 코드 포맷팅 및 임포트 정리 * chore: MyPageHomeViewModel 및 UI State 클래스 스타일 수정 --- .../kikidan/mypage/ExampleInstrumentedTest.kt | 6 +-- .../mypage/home/MyPageHomeViewModel.kt | 48 ++++++++++--------- .../mypage/home/model/MyPageHomeUiModel.kt | 4 +- .../mypage/home/model/MyPageHomeUiState.kt | 10 +++- .../com/kikidan/mypage/ExampleUnitTest.kt | 3 +- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt b/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt index babd854..437840d 100644 --- a/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt +++ b/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.kikidan.mypage -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt index dd5d019..a39fa04 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt @@ -12,33 +12,37 @@ import org.orbitmvi.orbit.syntax.Syntax import org.orbitmvi.orbit.viewmodel.container import javax.inject.Inject - - private typealias MyPageHomeSyntax = Syntax @HiltViewModel -class MyPageHomeViewModel @Inject constructor( - private val getMyPageInfoUseCase: GetMyPageInfoUseCase, -) : ViewModel(), ContainerHost { - override val container: Container = container(MyPageHomeUiState.Loading) { - repeatOnSubscription { - loadMyPageInfo() - } - } - - fun loadMyPageInfo() = intent { - getMyPageInfoUseCase() - .onSuccess { info -> - reduce { MyPageHomeUiState.Success(MyPageHomeUiModel(info.user, info.sajuPalja)) } +class MyPageHomeViewModel + @Inject + constructor( + private val getMyPageInfoUseCase: GetMyPageInfoUseCase, + ) : ViewModel(), + ContainerHost { + override val container: Container = + container(MyPageHomeUiState.Loading) { + repeatOnSubscription { + loadMyPageInfo() + } } - .onFailure { throwable -> - reduce { MyPageHomeUiState.Fail(throwable) } + + fun loadMyPageInfo() = + intent { + getMyPageInfoUseCase() + .onSuccess { info -> + reduce { MyPageHomeUiState.Success(MyPageHomeUiModel(info.user, info.sajuPalja)) } + }.onFailure { throwable -> + reduce { MyPageHomeUiState.Fail(throwable) } + } } - } - private suspend fun MyPageHomeSyntax.reduceIfSuccess(reducer: MyPageHomeUiState.Success.() -> MyPageHomeUiState) { - reduce { - (state as? MyPageHomeUiState.Success)?.reducer() ?: state + private suspend fun MyPageHomeSyntax.reduceIfSuccess( + reducer: MyPageHomeUiState.Success.() -> MyPageHomeUiState, + ) { + reduce { + (state as? MyPageHomeUiState.Success)?.reducer() ?: state + } } } -} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt index 245c883..b9fa8b1 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt @@ -3,9 +3,7 @@ package com.kikidan.mypage.home.model import com.kikidan.domain.model.saju.SajuPalja import com.kikidan.domain.model.user.User - - data class MyPageHomeUiModel( val user: User, - val sajuPalja: SajuPalja + val sajuPalja: SajuPalja, ) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt index 7a9d91d..f5f510e 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiState.kt @@ -2,6 +2,12 @@ package com.kikidan.mypage.home.model sealed interface MyPageHomeUiState { data object Loading : MyPageHomeUiState - data class Success(val model: MyPageHomeUiModel) : MyPageHomeUiState - data class Fail(val e: Throwable) : MyPageHomeUiState + + data class Success( + val model: MyPageHomeUiModel, + ) : MyPageHomeUiState + + data class Fail( + val e: Throwable, + ) : MyPageHomeUiState } diff --git a/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt b/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt index a62a916..c74490d 100644 --- a/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt +++ b/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt @@ -1,8 +1,7 @@ package com.kikidan.mypage -import org.junit.Test - import org.junit.Assert.* +import org.junit.Test /** * Example local unit test, which will execute on the development machine (host). From ed8fa0865a231ae4f79a542e8dda30d93fb5875f Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:38:45 +0900 Subject: [PATCH 05/22] remove example tests from mypage module --- .../kikidan/mypage/ExampleInstrumentedTest.kt | 22 ------------------- .../com/kikidan/mypage/ExampleUnitTest.kt | 16 -------------- 2 files changed, 38 deletions(-) delete mode 100644 feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt delete mode 100644 feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt diff --git a/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt b/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt deleted file mode 100644 index 437840d..0000000 --- a/feature/mypage/src/androidTest/java/com/kikidan/mypage/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.kikidan.mypage - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.* -import org.junit.Test -import org.junit.runner.RunWith - -/** - * 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.kikidan.mypage.test", appContext.packageName) - } -} diff --git a/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt b/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt deleted file mode 100644 index c74490d..0000000 --- a/feature/mypage/src/test/java/com/kikidan/mypage/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.kikidan.mypage - -import org.junit.Assert.* -import org.junit.Test - -/** - * 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) - } -} From 1a79909332c4eca09ddc51f2f1aa57e61f5c4207 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:28:41 +0900 Subject: [PATCH 06/22] register feature convention plugin in version catalog --- feature/mypage/build.gradle.kts | 3 ++- gradle/libs.versions.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index f0d4cbf..c32b8bb 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -1,7 +1,8 @@ plugins { - id("todakun.feature") + alias(libs.plugins.todakun.feature) } android { namespace = "com.kikidan.mypage" } + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81712ff..8c67c8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,4 +85,5 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradlePlugin" } todakun-android-signing = { id = "todakun.android.signing" } +todakun-feature = { id = "todakun.feature" } From ff5beae38eb459b479b72eab793118e9bd029640 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:34:12 +0900 Subject: [PATCH 07/22] feat: add ic_manage_saju_info icon --- .../main/res/drawable/ic_manage_saju_info.xml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable/ic_manage_saju_info.xml diff --git a/core/designsystem/src/main/res/drawable/ic_manage_saju_info.xml b/core/designsystem/src/main/res/drawable/ic_manage_saju_info.xml new file mode 100644 index 0000000..5886812 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_manage_saju_info.xml @@ -0,0 +1,45 @@ + + + + + + + From c0bc03feabd4acd0970dcb6a12d2c9b43c0c2a55 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:38:05 +0900 Subject: [PATCH 08/22] add ic_setting drawable --- .../src/main/res/drawable/ic_setting.xml | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable/ic_setting.xml diff --git a/core/designsystem/src/main/res/drawable/ic_setting.xml b/core/designsystem/src/main/res/drawable/ic_setting.xml new file mode 100644 index 0000000..2a6fafe --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_setting.xml @@ -0,0 +1,29 @@ + + + + + From 1bf24ac70bbe66480f054730dccdb54a5b87ba4c Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:40:18 +0900 Subject: [PATCH 09/22] add mail icon --- .../src/main/res/drawable/ic_mail.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable/ic_mail.xml diff --git a/core/designsystem/src/main/res/drawable/ic_mail.xml b/core/designsystem/src/main/res/drawable/ic_mail.xml new file mode 100644 index 0000000..152a94a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_mail.xml @@ -0,0 +1,33 @@ + + + + + From 1dc9b6f89bf2824ebd6eb28fdbc0f9d10e92137c Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:41:14 +0900 Subject: [PATCH 10/22] add ic_logout icon --- .../src/main/res/drawable/ic_logout.xml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable/ic_logout.xml diff --git a/core/designsystem/src/main/res/drawable/ic_logout.xml b/core/designsystem/src/main/res/drawable/ic_logout.xml new file mode 100644 index 0000000..6ca2812 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,39 @@ + + + + + + From e6d3e05331e5ec1d4526f95136310a6d92f433f0 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:06:22 +0900 Subject: [PATCH 11/22] add ic_chevron_right drawable --- .../main/res/drawable/ic_chevron_right.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 core/designsystem/src/main/res/drawable/ic_chevron_right.xml diff --git a/core/designsystem/src/main/res/drawable/ic_chevron_right.xml b/core/designsystem/src/main/res/drawable/ic_chevron_right.xml new file mode 100644 index 0000000..2b42b44 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_chevron_right.xml @@ -0,0 +1,24 @@ + + + + From 501ccf20aadc7b562cb140ad829c4855894e140d Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:30:21 +0900 Subject: [PATCH 12/22] add MyPage and common string resources --- .../src/main/res/values/strings.xml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index c9eade1..1670ad0 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -47,4 +47,25 @@ 토닥이 행운 액션 마이 + + + My page + 사주 정보 관리 + 알림 설정 + 앱 설정 + 1:1 문의 + 로그아웃 + 앱 버전 + v1.0.0 (최신 버전) + 만세력 보기 + 수정 + + + · + 남성 + 여성 + 양력 + 음력 + %1$d시 %2$02d분 + -%1$s, %2$s From 4509c41ef5f081d20e6ebfce1d929b020ae2ec45 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:30:42 +0900 Subject: [PATCH 13/22] =?UTF-8?q?[Domain]=20User=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EB=82=B4=20name=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: User 모델에 name 프로퍼티 추가 * feat: FakeUserRepository에 테스트용 이름 반영 --- .../main/java/com/kikidan/data/repository/FakeUserRepository.kt | 1 + core/domain/src/main/java/com/kikidan/domain/model/user/User.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/core/data/src/main/java/com/kikidan/data/repository/FakeUserRepository.kt b/core/data/src/main/java/com/kikidan/data/repository/FakeUserRepository.kt index f815cc1..c7d46a3 100644 --- a/core/data/src/main/java/com/kikidan/data/repository/FakeUserRepository.kt +++ b/core/data/src/main/java/com/kikidan/data/repository/FakeUserRepository.kt @@ -16,6 +16,7 @@ class FakeUserRepository Result.success( User( id = "fake-user-id", + name = "토닥이", gender = Gender.FEMALE, birth = Birth( diff --git a/core/domain/src/main/java/com/kikidan/domain/model/user/User.kt b/core/domain/src/main/java/com/kikidan/domain/model/user/User.kt index 8588949..760f193 100644 --- a/core/domain/src/main/java/com/kikidan/domain/model/user/User.kt +++ b/core/domain/src/main/java/com/kikidan/domain/model/user/User.kt @@ -5,6 +5,7 @@ import java.time.LocalTime data class User( val id: String, + val name: String, val gender: Gender, val birth: Birth, ) From 01a15cf159476f140663965ac32131175f9a1d69 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:31:01 +0900 Subject: [PATCH 14/22] [Feat] Implement MyPage home screen UI * feat: add MyPageHomeRoute and MyPageHomeScreen to handle UI states * feat: implement MyPageMenuItem and SajuCard reusable components * feat: add MyPageHomeHeader for the MyPage landing screen * feat: implement profile card and menu list layout in MyPageHomeScreen --- .../kikidan/mypage/home/ui/MyPageHomeRoute.kt | 18 ++ .../mypage/home/ui/MyPageHomeScreen.kt | 211 ++++++++++++++++++ .../home/ui/component/MyPageHomeHeader.kt | 23 ++ .../home/ui/component/MyPageMenuItem.kt | 62 +++++ .../mypage/home/ui/component/SajuCard.kt | 77 +++++++ 5 files changed, 391 insertions(+) create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageMenuItem.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt new file mode 100644 index 0000000..b34d4ec --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt @@ -0,0 +1,18 @@ +package com.kikidan.mypage.home.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.hilt.navigation.compose.hiltViewModel +import com.kikidan.mypage.home.MyPageHomeViewModel +import org.orbitmvi.orbit.compose.collectAsState + +@Composable +fun MyPageHomeRoute( + viewModel: MyPageHomeViewModel = hiltViewModel(), +) { + val uiState by viewModel.collectAsState() + + MyPageHomeScreen( + uiState = uiState, + ) +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt new file mode 100644 index 0000000..146d053 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt @@ -0,0 +1,211 @@ +package com.kikidan.mypage.home.ui + +import androidx.compose.foundation.background +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.R +import com.kikidan.designsystem.component.TodakunDivider +import com.kikidan.designsystem.component.TodakunDividerType +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTheme +import com.kikidan.designsystem.theme.TodakunTypography +import com.kikidan.domain.model.saju.CheonGan +import com.kikidan.domain.model.saju.JiJi +import com.kikidan.domain.model.saju.SajuPalja +import com.kikidan.domain.model.saju.SajuPillar +import com.kikidan.domain.model.user.Birth +import com.kikidan.domain.model.user.BirthTime +import com.kikidan.domain.model.user.DateType +import com.kikidan.domain.model.user.Gender +import com.kikidan.domain.model.user.User +import com.kikidan.mypage.home.model.MyPageHomeUiModel +import com.kikidan.mypage.home.model.MyPageHomeUiState +import com.kikidan.mypage.home.ui.component.MyPageHomeHeader +import com.kikidan.mypage.home.ui.component.MyPageMenuItem +import com.kikidan.mypage.home.ui.component.ProfileCard +import java.time.LocalDate + +@Composable +fun MyPageHomeScreen( + uiState: MyPageHomeUiState, + modifier: Modifier = Modifier, + onEditClick: () -> Unit = {}, + onViewMansaeryeokClick: () -> Unit = {}, + onSajuInfoClick: () -> Unit = {}, + onNotificationSettingClick: () -> Unit = {}, + onAppSettingClick: () -> Unit = {}, + onInquiryClick: () -> Unit = {}, + onLogoutClick: () -> Unit = {}, +) { + Column( + modifier = + modifier + .fillMaxSize() + .background(TodakunColor.white), + ) { + MyPageHomeHeader(modifier = Modifier.fillMaxWidth()) + + when (uiState) { + is MyPageHomeUiState.Loading -> MyPageHomeLoading(modifier = Modifier.weight(1f)) + is MyPageHomeUiState.Fail -> MyPageHomeError(modifier = Modifier.weight(1f)) + is MyPageHomeUiState.Success -> + MyPageHomeContent( + model = uiState.model, + onEditClick = onEditClick, + onViewMansaeryeokClick = onViewMansaeryeokClick, + onSajuInfoClick = onSajuInfoClick, + onNotificationSettingClick = onNotificationSettingClick, + onAppSettingClick = onAppSettingClick, + onInquiryClick = onInquiryClick, + onLogoutClick = onLogoutClick, + modifier = Modifier.weight(1f), + ) + } + } +} + +@Composable +private fun MyPageHomeLoading(modifier: Modifier = Modifier) { + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center){ + + } +} + +@Composable +private fun MyPageHomeError(modifier: Modifier = Modifier) { + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + + } +} + +@Composable +private fun MyPageHomeContent( + model: MyPageHomeUiModel, + onEditClick: () -> Unit, + onViewMansaeryeokClick: () -> Unit, + onSajuInfoClick: () -> Unit, + onNotificationSettingClick: () -> Unit, + onAppSettingClick: () -> Unit, + onInquiryClick: () -> Unit, + onLogoutClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.fillMaxSize()) { + ProfileCard( + user = model.user, + sajuPalja = model.sajuPalja, + onEditClick = onEditClick, + onViewMansaeryeokClick = onViewMansaeryeokClick, + modifier = Modifier.padding(horizontal = 20.dp), + ) + + Spacer(modifier = Modifier.height(24.dp)) + TodakunDivider(type = TodakunDividerType.Section) + + MyPageMenuItem( + painter = painterResource(R.drawable.ic_manage_saju_info), + label = stringResource(R.string.mypage_menu_saju_info), + onClick = onSajuInfoClick, + modifier = Modifier.padding(horizontal = 20.dp), + ) + MyPageMenuItem( + painter = painterResource(R.drawable.ic_bell), + label = stringResource(R.string.mypage_menu_notification_setting), + onClick = onNotificationSettingClick, + modifier = Modifier.padding(horizontal = 20.dp), + ) + MyPageMenuItem( + painter = painterResource(R.drawable.ic_setting), + label = stringResource(R.string.mypage_menu_app_setting), + onClick = onAppSettingClick, + modifier = Modifier.padding(horizontal = 20.dp), + ) + MyPageMenuItem( + painter = painterResource(R.drawable.ic_mail), + label = stringResource(R.string.mypage_menu_inquiry), + onClick = onInquiryClick, + modifier = Modifier.padding(horizontal = 20.dp), + ) + MyPageMenuItem( + painter = painterResource(R.drawable.ic_logout), + label = stringResource(R.string.mypage_menu_logout), + onClick = onLogoutClick, + showChevron = false, + textColor = TodakunColor.gray600, + iconTint = TodakunColor.gray500, + modifier = Modifier.padding(horizontal = 20.dp), + ) + + Spacer(modifier = Modifier.weight(1f)) + + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 20.dp), + horizontalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(R.string.mypage_app_version_label), + style = TodakunTypography.caption1Regular, + color = TodakunColor.gray400, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.mypage_app_version_value), + style = TodakunTypography.caption1Regular, + color = TodakunColor.gray400, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun MyPageHomeScreenPreview() { + TodakunTheme { + MyPageHomeScreen( + uiState = + MyPageHomeUiState.Success( + MyPageHomeUiModel( + user = + User( + id = "preview", + name = "토닥이", + gender = Gender.FEMALE, + birth = + Birth( + dateType = DateType.SOLAR, + date = LocalDate.of(1999, 2, 13), + time = BirthTime.SIN, + ), + ), + sajuPalja = + SajuPalja( + yearPillar = SajuPillar(CheonGan.SIN, JiJi.MI), + monthPillar = SajuPillar(CheonGan.GI, JiJi.SA), + dayPillar = SajuPillar(CheonGan.GYE, JiJi.MYO), + hourPillar = SajuPillar(CheonGan.JEONG, JiJi.CHUK), + ), + ), + ), + ) + } +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt new file mode 100644 index 0000000..eb53971 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt @@ -0,0 +1,23 @@ +package com.kikidan.mypage.home.ui.component + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.R +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTypography + +@Composable +internal fun MyPageHomeHeader( + modifier: Modifier = Modifier, +) { + Text( + text = stringResource(R.string.mypage_home_title), + style = TodakunTypography.heading4Bold, + color = TodakunColor.black, + modifier = modifier.padding(horizontal = 20.dp, vertical = 16.dp), + ) +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageMenuItem.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageMenuItem.kt new file mode 100644 index 0000000..b8b4ec4 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageMenuItem.kt @@ -0,0 +1,62 @@ +package com.kikidan.mypage.home.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.R +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTypography + +@Composable +internal fun MyPageMenuItem( + painter: Painter, + label: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + iconTint: Color = TodakunColor.gray975, + textColor: Color = TodakunColor.black, + showChevron: Boolean = true, +) { + Row( + modifier = + modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painter, + contentDescription = null, + tint = iconTint, + modifier = Modifier.size(20.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = label, + style = TodakunTypography.body2Medium, + color = textColor, + modifier = Modifier.weight(1f), + ) + if (showChevron) { + Icon( + painter = painterResource(R.drawable.ic_chevron_right), + contentDescription = null, + tint = TodakunColor.gray400, + ) + } + } +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt new file mode 100644 index 0000000..7c22a98 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt @@ -0,0 +1,77 @@ +package com.kikidan.mypage.home.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.R +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTheme +import com.kikidan.designsystem.theme.TodakunTypography +import com.kikidan.domain.model.saju.Ohaeng + +@Composable +internal fun SajuCard( + hanja: String, + reading: String, + ohaeng: Ohaeng, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(12.dp)) + .background(ohaeng.containerColor()) + .padding(horizontal = 6.dp, vertical = 9.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text( + text = hanja, + style = TodakunTypography.body1Bold, + color = TodakunColor.gray975 + ) + Text( + text = stringResource(R.string.saju_card_label_format, reading, ohaeng.hanja), + style = TodakunTypography.caption3Regular, + color = TodakunColor.gray975 + ) + } +} + +private fun Ohaeng.containerColor(): Color = + when (this) { + Ohaeng.MOK -> TodakunColor.teal200 + Ohaeng.HWA -> TodakunColor.red200 + Ohaeng.TO -> TodakunColor.orange200 + Ohaeng.GEUM -> TodakunColor.coolGray300 + Ohaeng.SU -> TodakunColor.sky200 + } + +@Preview(showBackground = true) +@Composable +private fun SajuCardPreview() { + TodakunTheme { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SajuCard(hanja = "辛", reading = "신", ohaeng = Ohaeng.GEUM, modifier = Modifier.width(72.dp)) + SajuCard(hanja = "己", reading = "기", ohaeng = Ohaeng.TO, modifier = Modifier.width(72.dp)) + SajuCard(hanja = "癸", reading = "계", ohaeng = Ohaeng.SU, modifier = Modifier.width(72.dp)) + SajuCard(hanja = "丁", reading = "정", ohaeng = Ohaeng.HWA, modifier = Modifier.width(72.dp)) + SajuCard(hanja = "卯", reading = "묘", ohaeng = Ohaeng.MOK, modifier = Modifier.width(72.dp)) + } + } +} From a2bb6bb5d282b5fe49c1a90cf4a4c2c2aa8212ac Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:50:12 +0900 Subject: [PATCH 15/22] implement MyPage home UI components and refactor menu logic * feat: add `MyPageMenuType` enum to manage menu resources and styles * feat: add `ProfileCard` and `SajuPaljaGrid` UI components to display user information and Saju data * refactor: update `MyPageHomeScreen` to render menu items dynamically using `MyPageMenuType` * refactor: simplify navigation callbacks in `MyPageHomeScreen` by using a single menu click listener --- .../mypage/home/model/MyPageMenuType.kt | 27 +++ .../mypage/home/ui/MyPageHomeScreen.kt | 63 ++---- .../mypage/home/ui/component/ProfileCard.kt | 212 ++++++++++++++++++ .../mypage/home/ui/component/SajuPaljaGrid.kt | 76 +++++++ 4 files changed, 330 insertions(+), 48 deletions(-) create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt new file mode 100644 index 0000000..a7ae68b --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt @@ -0,0 +1,27 @@ +package com.kikidan.mypage.home.model + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.Color +import com.kikidan.designsystem.R +import com.kikidan.designsystem.theme.TodakunColor + +enum class MyPageMenuType( + @DrawableRes val iconRes: Int, + @StringRes val labelRes: Int, + val showChevron: Boolean = true, + val iconTint: Color = TodakunColor.gray975, + val textColor: Color = TodakunColor.black, +) { + SAJU_INFO(iconRes = R.drawable.ic_manage_saju_info, labelRes = R.string.mypage_menu_saju_info), + NOTIFICATION_SETTING(iconRes = R.drawable.ic_bell, labelRes = R.string.mypage_menu_notification_setting), + APP_SETTING(iconRes = R.drawable.ic_setting, labelRes = R.string.mypage_menu_app_setting), + INQUIRY(iconRes = R.drawable.ic_mail, labelRes = R.string.mypage_menu_inquiry), + LOGOUT( + iconRes = R.drawable.ic_logout, + labelRes = R.string.mypage_menu_logout, + showChevron = false, + iconTint = TodakunColor.gray500, + textColor = TodakunColor.gray600, + ), +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt index 146d053..ec40a70 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt @@ -37,6 +37,7 @@ import com.kikidan.domain.model.user.Gender import com.kikidan.domain.model.user.User import com.kikidan.mypage.home.model.MyPageHomeUiModel import com.kikidan.mypage.home.model.MyPageHomeUiState +import com.kikidan.mypage.home.model.MyPageMenuType import com.kikidan.mypage.home.ui.component.MyPageHomeHeader import com.kikidan.mypage.home.ui.component.MyPageMenuItem import com.kikidan.mypage.home.ui.component.ProfileCard @@ -48,11 +49,7 @@ fun MyPageHomeScreen( modifier: Modifier = Modifier, onEditClick: () -> Unit = {}, onViewMansaeryeokClick: () -> Unit = {}, - onSajuInfoClick: () -> Unit = {}, - onNotificationSettingClick: () -> Unit = {}, - onAppSettingClick: () -> Unit = {}, - onInquiryClick: () -> Unit = {}, - onLogoutClick: () -> Unit = {}, + onMenuItemClick: (MyPageMenuType) -> Unit = {}, ) { Column( modifier = @@ -70,11 +67,7 @@ fun MyPageHomeScreen( model = uiState.model, onEditClick = onEditClick, onViewMansaeryeokClick = onViewMansaeryeokClick, - onSajuInfoClick = onSajuInfoClick, - onNotificationSettingClick = onNotificationSettingClick, - onAppSettingClick = onAppSettingClick, - onInquiryClick = onInquiryClick, - onLogoutClick = onLogoutClick, + onMenuItemClick = onMenuItemClick, modifier = Modifier.weight(1f), ) } @@ -100,11 +93,7 @@ private fun MyPageHomeContent( model: MyPageHomeUiModel, onEditClick: () -> Unit, onViewMansaeryeokClick: () -> Unit, - onSajuInfoClick: () -> Unit, - onNotificationSettingClick: () -> Unit, - onAppSettingClick: () -> Unit, - onInquiryClick: () -> Unit, - onLogoutClick: () -> Unit, + onMenuItemClick: (MyPageMenuType) -> Unit, modifier: Modifier = Modifier, ) { Column(modifier = modifier.fillMaxSize()) { @@ -119,39 +108,17 @@ private fun MyPageHomeContent( Spacer(modifier = Modifier.height(24.dp)) TodakunDivider(type = TodakunDividerType.Section) - MyPageMenuItem( - painter = painterResource(R.drawable.ic_manage_saju_info), - label = stringResource(R.string.mypage_menu_saju_info), - onClick = onSajuInfoClick, - modifier = Modifier.padding(horizontal = 20.dp), - ) - MyPageMenuItem( - painter = painterResource(R.drawable.ic_bell), - label = stringResource(R.string.mypage_menu_notification_setting), - onClick = onNotificationSettingClick, - modifier = Modifier.padding(horizontal = 20.dp), - ) - MyPageMenuItem( - painter = painterResource(R.drawable.ic_setting), - label = stringResource(R.string.mypage_menu_app_setting), - onClick = onAppSettingClick, - modifier = Modifier.padding(horizontal = 20.dp), - ) - MyPageMenuItem( - painter = painterResource(R.drawable.ic_mail), - label = stringResource(R.string.mypage_menu_inquiry), - onClick = onInquiryClick, - modifier = Modifier.padding(horizontal = 20.dp), - ) - MyPageMenuItem( - painter = painterResource(R.drawable.ic_logout), - label = stringResource(R.string.mypage_menu_logout), - onClick = onLogoutClick, - showChevron = false, - textColor = TodakunColor.gray600, - iconTint = TodakunColor.gray500, - modifier = Modifier.padding(horizontal = 20.dp), - ) + MyPageMenuType.entries.forEach { menuType -> + MyPageMenuItem( + painter = painterResource(menuType.iconRes), + label = stringResource(menuType.labelRes), + onClick = { onMenuItemClick(menuType) }, + showChevron = menuType.showChevron, + iconTint = menuType.iconTint, + textColor = menuType.textColor, + modifier = Modifier.padding(horizontal = 20.dp), + ) + } Spacer(modifier = Modifier.weight(1f)) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt new file mode 100644 index 0000000..2971bc5 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt @@ -0,0 +1,212 @@ +package com.kikidan.mypage.home.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.R +import com.kikidan.designsystem.component.button.PrimaryButton +import com.kikidan.designsystem.component.button.TodakunButtonSize +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTheme +import com.kikidan.designsystem.theme.TodakunTypography +import com.kikidan.domain.model.saju.CheonGan +import com.kikidan.domain.model.saju.JiJi +import com.kikidan.domain.model.saju.SajuPalja +import com.kikidan.domain.model.saju.SajuPillar +import com.kikidan.domain.model.user.Birth +import com.kikidan.domain.model.user.BirthTime +import com.kikidan.domain.model.user.DateType +import com.kikidan.domain.model.user.Gender +import com.kikidan.domain.model.user.User +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Composable +internal fun ProfileCard( + user: User, + sajuPalja: SajuPalja, + onEditClick: () -> Unit, + onViewMansaeryeokClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier + .fillMaxWidth() + .clip(RoundedCornerShape(20.dp)) + .background(TodakunColor.white) + .border(1.dp, TodakunColor.gray100, RoundedCornerShape(20.dp)) + .padding(20.dp), + ) { + Row(verticalAlignment = Alignment.Top) { + Column(modifier = Modifier.weight(1f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = user.name, + style = TodakunTypography.heading3Bold, + color = TodakunColor.gray975 + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.separator_dot), + style = TodakunTypography.body2Regular, + color = TodakunColor.gray700 + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = user.gender.displayName(), + style = TodakunTypography.body2Regular, + color = TodakunColor.gray700, + ) + } + Spacer(modifier = Modifier.height(4.dp)) + UserBirthDateTime(birth = user.birth) + } + EditButton(onClick = onEditClick) + } + + Spacer(modifier = Modifier.height(20.dp)) + SajuPaljaGrid(sajuPalja = sajuPalja) + Spacer(modifier = Modifier.height(20.dp)) + + PrimaryButton( + text = stringResource(R.string.mypage_view_mansaeryeok_button), + onClick = onViewMansaeryeokClick, + size = TodakunButtonSize.Medium, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun UserBirthDateTime( + birth: Birth +) { + val dateText = "${birth.date.format(BirthDateFormatter)} ${birth.dateType.displayName()}" + + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = dateText, + style = TodakunTypography.body3Medium, + color = TodakunColor.gray700, + ) + if (birth.time != BirthTime.UNKNOWN) { + Text( + text = stringResource(R.string.separator_dot), + style = TodakunTypography.body3Medium, + color = TodakunColor.gray700, + modifier = Modifier.padding(horizontal = 8.dp) + ) + Text( + text = birth.time.displayText(), + style = TodakunTypography.body3Medium, + color = TodakunColor.gray700 + ) + } + } +} + +@Composable +@Preview(showBackground = true) +private fun UserBirthDateTimePreview() { + val birth = Birth( + dateType = DateType.SOLAR, + date = LocalDate.of(1999, 2, 13), + time = BirthTime.SIN, + ) + UserBirthDateTime( + birth = birth + ) +} + +@Composable +private fun EditButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + Box( + modifier = + modifier + .clip(RoundedCornerShape(8.dp)) + .border( + 1.dp, + TodakunColor.primary500, + RoundedCornerShape(8.dp) + ) + .clickable(onClick = onClick) + .background(TodakunColor.white) + .padding(horizontal = 19.dp, vertical = 8.dp), + ) { + Text( + text = stringResource(R.string.mypage_edit_button), + style = TodakunTypography.caption1SemiBold, + color = TodakunColor.primary700 + ) + } +} + +private val BirthDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") + +@Composable +private fun Gender.displayName(): String = + when (this) { + Gender.MALE -> stringResource(R.string.gender_male) + Gender.FEMALE -> stringResource(R.string.gender_female) + } + +@Composable +private fun DateType.displayName(): String = + when (this) { + DateType.SOLAR -> stringResource(R.string.date_type_solar) + DateType.LUNAR -> stringResource(R.string.date_type_lunar) + } + +@Composable +private fun BirthTime.displayText(): String { + val start = startTime ?: return "" + return stringResource(R.string.birth_time_format, start.hour, start.minute) +} + +@Preview(showBackground = true) +@Composable +private fun ProfileCardPreview() { + TodakunTheme { + ProfileCard( + user = + User( + id = "preview", + name = "토닥이", + gender = Gender.FEMALE, + birth = + Birth( + dateType = DateType.SOLAR, + date = LocalDate.of(1999, 2, 13), + time = BirthTime.SIN, + ), + ), + sajuPalja = + SajuPalja( + yearPillar = SajuPillar(CheonGan.SIN, JiJi.MI), + monthPillar = SajuPillar(CheonGan.GI, JiJi.SA), + dayPillar = SajuPillar(CheonGan.GYE, JiJi.MYO), + hourPillar = SajuPillar(CheonGan.JEONG, JiJi.CHUK), + ), + onEditClick = {}, + onViewMansaeryeokClick = {}, + ) + } +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt new file mode 100644 index 0000000..924fdf8 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt @@ -0,0 +1,76 @@ +package com.kikidan.mypage.home.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.kikidan.designsystem.theme.TodakunColor +import com.kikidan.designsystem.theme.TodakunTheme +import com.kikidan.domain.model.saju.CheonGan +import com.kikidan.domain.model.saju.JiJi +import com.kikidan.domain.model.saju.SajuPalja +import com.kikidan.domain.model.saju.SajuPillar + +@Composable +internal fun SajuPaljaGrid( + sajuPalja: SajuPalja, + modifier: Modifier = Modifier +) { + val pillars: List = + listOfNotNull(sajuPalja.yearPillar, sajuPalja.monthPillar, sajuPalja.dayPillar, sajuPalja.hourPillar) + + Column( + modifier = + modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(TodakunColor.coolGray50) + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + pillars.forEach { pillar -> + SajuCard( + hanja = pillar.cheonGan.hanja, + reading = pillar.cheonGan.displayName, + ohaeng = pillar.cheonGan.ohaeng, + modifier = Modifier.weight(1f), + ) + } + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + pillars.forEach { pillar -> + SajuCard( + hanja = pillar.jiJi.hanja, + reading = pillar.jiJi.displayName, + ohaeng = pillar.jiJi.ohaeng, + modifier = Modifier.weight(1f), + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SajuPaljaGridPreview() { + TodakunTheme { + SajuPaljaGrid( + sajuPalja = + SajuPalja( + yearPillar = SajuPillar(CheonGan.SIN, JiJi.MI), + monthPillar = SajuPillar(CheonGan.GI, JiJi.SA), + dayPillar = SajuPillar(CheonGan.GYE, JiJi.MYO), + hourPillar = SajuPillar(CheonGan.JEONG, JiJi.CHUK), + ), + ) + } +} From a7d84914513ac694a65024fcfab217a0f5a0f3cc Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:52:00 +0900 Subject: [PATCH 16/22] add @param use-site target to MyPageMenuType annotations --- .../main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt index a7ae68b..90c7535 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageMenuType.kt @@ -7,8 +7,8 @@ import com.kikidan.designsystem.R import com.kikidan.designsystem.theme.TodakunColor enum class MyPageMenuType( - @DrawableRes val iconRes: Int, - @StringRes val labelRes: Int, + @param:DrawableRes val iconRes: Int, + @param:StringRes val labelRes: Int, val showChevron: Boolean = true, val iconTint: Color = TodakunColor.gray975, val textColor: Color = TodakunColor.black, From 7f81902e8d6819d779e37f9424861fde78eba396 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:04:02 +0900 Subject: [PATCH 17/22] add comments for loading and error states in MyPageHomeScreen --- .../main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt index ec40a70..cbc42d1 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt @@ -76,6 +76,7 @@ fun MyPageHomeScreen( @Composable private fun MyPageHomeLoading(modifier: Modifier = Modifier) { + //로딩 요구사항 없음 Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center){ } @@ -83,6 +84,7 @@ private fun MyPageHomeLoading(modifier: Modifier = Modifier) { @Composable private fun MyPageHomeError(modifier: Modifier = Modifier) { + //에러 요구사항 없음 Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { } From f90b6807b9d17a2e7bf72fc4f89fbc1ef48c3040 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:49:31 +0900 Subject: [PATCH 18/22] =?UTF-8?q?[Feat]=20mypage=20feature=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EB=B0=8F=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: app 모듈에 feature:mypage 의존성 추가 * refactor: MyPageHomeRoute에 Modifier 파라미터 추가 및 전달 --- app/build.gradle.kts | 1 + .../main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e3d769..0cdc27a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { implementation(projects.core.dataLocal) implementation(projects.core.navigation) implementation(projects.core.designsystem) + implementation(projects.feature.mypage) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.navigation3.ui) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt index b34d4ec..c3f9ada 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeRoute.kt @@ -2,17 +2,20 @@ package com.kikidan.mypage.home.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import com.kikidan.mypage.home.MyPageHomeViewModel import org.orbitmvi.orbit.compose.collectAsState @Composable fun MyPageHomeRoute( + modifier: Modifier = Modifier, viewModel: MyPageHomeViewModel = hiltViewModel(), ) { val uiState by viewModel.collectAsState() MyPageHomeScreen( uiState = uiState, + modifier = modifier, ) } From 46ef86ea5b01d0674288f17995aa3d8e741631a5 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:55:17 +0900 Subject: [PATCH 19/22] [Style] reformat code and apply trailing commas in mypage feature * apply trailing commas to UI components and function calls * reformat function signatures and when expressions for consistency * clean up spacing in comments and remove unused lines --- feature/mypage/build.gradle.kts | 1 - .../mypage/home/ui/MyPageHomeScreen.kt | 21 +++++++---- .../home/ui/component/MyPageHomeHeader.kt | 4 +- .../mypage/home/ui/component/ProfileCard.kt | 37 ++++++++++--------- .../mypage/home/ui/component/SajuCard.kt | 4 +- .../mypage/home/ui/component/SajuPaljaGrid.kt | 2 +- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index c32b8bb..acec1f7 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -5,4 +5,3 @@ plugins { android { namespace = "com.kikidan.mypage" } - diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt index cbc42d1..ef6bbdf 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt @@ -60,9 +60,15 @@ fun MyPageHomeScreen( MyPageHomeHeader(modifier = Modifier.fillMaxWidth()) when (uiState) { - is MyPageHomeUiState.Loading -> MyPageHomeLoading(modifier = Modifier.weight(1f)) - is MyPageHomeUiState.Fail -> MyPageHomeError(modifier = Modifier.weight(1f)) - is MyPageHomeUiState.Success -> + is MyPageHomeUiState.Loading -> { + MyPageHomeLoading(modifier = Modifier.weight(1f)) + } + + is MyPageHomeUiState.Fail -> { + MyPageHomeError(modifier = Modifier.weight(1f)) + } + + is MyPageHomeUiState.Success -> { MyPageHomeContent( model = uiState.model, onEditClick = onEditClick, @@ -70,23 +76,22 @@ fun MyPageHomeScreen( onMenuItemClick = onMenuItemClick, modifier = Modifier.weight(1f), ) + } } } } @Composable private fun MyPageHomeLoading(modifier: Modifier = Modifier) { - //로딩 요구사항 없음 - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center){ - + // 로딩 요구사항 없음 + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { } } @Composable private fun MyPageHomeError(modifier: Modifier = Modifier) { - //에러 요구사항 없음 + // 에러 요구사항 없음 Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - } } diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt index eb53971..6250dbe 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/MyPageHomeHeader.kt @@ -11,9 +11,7 @@ import com.kikidan.designsystem.theme.TodakunColor import com.kikidan.designsystem.theme.TodakunTypography @Composable -internal fun MyPageHomeHeader( - modifier: Modifier = Modifier, -) { +internal fun MyPageHomeHeader(modifier: Modifier = Modifier) { Text( text = stringResource(R.string.mypage_home_title), style = TodakunTypography.heading4Bold, diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt index 2971bc5..87ba42b 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/ProfileCard.kt @@ -61,13 +61,13 @@ internal fun ProfileCard( Text( text = user.name, style = TodakunTypography.heading3Bold, - color = TodakunColor.gray975 + color = TodakunColor.gray975, ) Spacer(modifier = Modifier.width(4.dp)) Text( text = stringResource(R.string.separator_dot), style = TodakunTypography.body2Regular, - color = TodakunColor.gray700 + color = TodakunColor.gray700, ) Spacer(modifier = Modifier.width(4.dp)) Text( @@ -96,9 +96,7 @@ internal fun ProfileCard( } @Composable -private fun UserBirthDateTime( - birth: Birth -) { +private fun UserBirthDateTime(birth: Birth) { val dateText = "${birth.date.format(BirthDateFormatter)} ${birth.dateType.displayName()}" Row(verticalAlignment = Alignment.CenterVertically) { @@ -112,12 +110,12 @@ private fun UserBirthDateTime( text = stringResource(R.string.separator_dot), style = TodakunTypography.body3Medium, color = TodakunColor.gray700, - modifier = Modifier.padding(horizontal = 8.dp) + modifier = Modifier.padding(horizontal = 8.dp), ) Text( text = birth.time.displayText(), style = TodakunTypography.body3Medium, - color = TodakunColor.gray700 + color = TodakunColor.gray700, ) } } @@ -126,18 +124,22 @@ private fun UserBirthDateTime( @Composable @Preview(showBackground = true) private fun UserBirthDateTimePreview() { - val birth = Birth( - dateType = DateType.SOLAR, - date = LocalDate.of(1999, 2, 13), - time = BirthTime.SIN, - ) + val birth = + Birth( + dateType = DateType.SOLAR, + date = LocalDate.of(1999, 2, 13), + time = BirthTime.SIN, + ) UserBirthDateTime( - birth = birth + birth = birth, ) } @Composable -private fun EditButton(onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun EditButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { Box( modifier = modifier @@ -145,16 +147,15 @@ private fun EditButton(onClick: () -> Unit, modifier: Modifier = Modifier) { .border( 1.dp, TodakunColor.primary500, - RoundedCornerShape(8.dp) - ) - .clickable(onClick = onClick) + RoundedCornerShape(8.dp), + ).clickable(onClick = onClick) .background(TodakunColor.white) .padding(horizontal = 19.dp, vertical = 8.dp), ) { Text( text = stringResource(R.string.mypage_edit_button), style = TodakunTypography.caption1SemiBold, - color = TodakunColor.primary700 + color = TodakunColor.primary700, ) } } diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt index 7c22a98..1c88575 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuCard.kt @@ -43,12 +43,12 @@ internal fun SajuCard( Text( text = hanja, style = TodakunTypography.body1Bold, - color = TodakunColor.gray975 + color = TodakunColor.gray975, ) Text( text = stringResource(R.string.saju_card_label_format, reading, ohaeng.hanja), style = TodakunTypography.caption3Regular, - color = TodakunColor.gray975 + color = TodakunColor.gray975, ) } } diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt index 924fdf8..55cd720 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/component/SajuPaljaGrid.kt @@ -22,7 +22,7 @@ import com.kikidan.domain.model.saju.SajuPillar @Composable internal fun SajuPaljaGrid( sajuPalja: SajuPalja, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val pillars: List = listOfNotNull(sajuPalja.yearPillar, sajuPalja.monthPillar, sajuPalja.dayPillar, sajuPalja.hourPillar) From 803f9745de14c3b0f88b080570fff7f011ce8be0 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:08:31 +0900 Subject: [PATCH 20/22] refactor: remove unused Orbit syntax and helper function in MyPageHomeViewModel --- .../com/kikidan/mypage/home/MyPageHomeViewModel.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt index a39fa04..df4274a 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt @@ -8,12 +8,9 @@ import com.kikidan.mypage.home.model.MyPageHomeUiState import dagger.hilt.android.lifecycle.HiltViewModel import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost -import org.orbitmvi.orbit.syntax.Syntax import org.orbitmvi.orbit.viewmodel.container import javax.inject.Inject -private typealias MyPageHomeSyntax = Syntax - @HiltViewModel class MyPageHomeViewModel @Inject @@ -37,12 +34,4 @@ class MyPageHomeViewModel reduce { MyPageHomeUiState.Fail(throwable) } } } - - private suspend fun MyPageHomeSyntax.reduceIfSuccess( - reducer: MyPageHomeUiState.Success.() -> MyPageHomeUiState, - ) { - reduce { - (state as? MyPageHomeUiState.Success)?.reducer() ?: state - } - } } From d36bb7fbe2e0fc6900d12eb95c4160c057515262 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:17:58 +0900 Subject: [PATCH 21/22] =?UTF-8?q?Chore:=20=EC=95=B1=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=20=EB=B2=84=EC=A0=84=20string=20resource=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/designsystem/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 1670ad0..010585b 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -56,7 +56,6 @@ 1:1 문의 로그아웃 앱 버전 - v1.0.0 (최신 버전) 만세력 보기 수정 From cf64267c06beec6a95f8a463ec6db356d389eb25 Mon Sep 17 00:00:00 2001 From: Moony-H <53536205+Moony-H@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:12:33 +0900 Subject: [PATCH 22/22] =?UTF-8?q?Fix:=20=EC=95=B1=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=ED=99=94=EB=A9=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/TodakunTab.kt | 11 +++++++ .../src/main/res/values/strings.xml | 2 ++ feature/mypage/build.gradle.kts | 5 ++++ .../kikidan/mypage/home/AppVersionChecker.kt | 30 +++++++++++++++++++ .../mypage/home/MyPageHomeViewModel.kt | 13 +++++++- .../mypage/home/model/MyPageHomeUiModel.kt | 2 ++ .../mypage/home/ui/MyPageHomeScreen.kt | 13 +++++--- gradle/libs.versions.toml | 3 ++ 8 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 feature/mypage/src/main/java/com/kikidan/mypage/home/AppVersionChecker.kt diff --git a/core/designsystem/src/main/java/com/kikidan/designsystem/component/TodakunTab.kt b/core/designsystem/src/main/java/com/kikidan/designsystem/component/TodakunTab.kt index 57df7cd..bcabff4 100644 --- a/core/designsystem/src/main/java/com/kikidan/designsystem/component/TodakunTab.kt +++ b/core/designsystem/src/main/java/com/kikidan/designsystem/component/TodakunTab.kt @@ -24,6 +24,14 @@ import com.kikidan.designsystem.theme.TodakunColor import com.kikidan.designsystem.theme.TodakunTheme import com.kikidan.designsystem.theme.TodakunTypography +/** + * A custom tab component for the Todakun design system. + * + * @param text The text to be displayed on the tab. + * @param selected Whether the tab is currently selected. + * @param onClick Callback to be invoked when the tab is clicked. + * @param modifier The modifier to be applied to the tab. + */ @Composable fun TodakunTab( text: String, @@ -62,6 +70,9 @@ fun TodakunTab( } } +/** + * Default values for [TodakunTab]. + */ private object TodakunTabDefaults { val Shape = RoundedCornerShape(99.dp) } diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 010585b..f2c3186 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -56,8 +56,10 @@ 1:1 문의 로그아웃 앱 버전 + v %1$s 만세력 보기 수정 + (최신 버전) · diff --git a/feature/mypage/build.gradle.kts b/feature/mypage/build.gradle.kts index acec1f7..95da1d6 100644 --- a/feature/mypage/build.gradle.kts +++ b/feature/mypage/build.gradle.kts @@ -5,3 +5,8 @@ plugins { android { namespace = "com.kikidan.mypage" } + +dependencies { + implementation(libs.play.app.update.ktx) + implementation(libs.kotlinx.coroutines.play.services) +} diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/AppVersionChecker.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/AppVersionChecker.kt new file mode 100644 index 0000000..db56857 --- /dev/null +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/AppVersionChecker.kt @@ -0,0 +1,30 @@ +package com.kikidan.mypage.home + +import android.content.Context +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.install.model.UpdateAvailability +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.tasks.await +import javax.inject.Inject + +class AppVersionChecker + @Inject + constructor( + @ApplicationContext private val context: Context, + ) { + fun getCurrentVersionName(): String = + context.packageManager + .getPackageInfo(context.packageName, 0) + .versionName + .orEmpty() + + suspend fun isLatestVersion(): Boolean = + try { + val appUpdateManager = AppUpdateManagerFactory.create(context) + val appUpdateInfo = appUpdateManager.appUpdateInfo.await() + appUpdateInfo.updateAvailability() != UpdateAvailability.UPDATE_AVAILABLE + } catch (e: Exception) { + // Play Store가 없는 기기(에뮬레이터 등)에서도 마이페이지 조회 자체는 실패하면 안 되므로 조회 실패 시 최신 버전으로 간주한다. + true + } + } diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt index df4274a..78e9cc5 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/MyPageHomeViewModel.kt @@ -16,6 +16,7 @@ class MyPageHomeViewModel @Inject constructor( private val getMyPageInfoUseCase: GetMyPageInfoUseCase, + private val appVersionChecker: AppVersionChecker, ) : ViewModel(), ContainerHost { override val container: Container = @@ -29,7 +30,17 @@ class MyPageHomeViewModel intent { getMyPageInfoUseCase() .onSuccess { info -> - reduce { MyPageHomeUiState.Success(MyPageHomeUiModel(info.user, info.sajuPalja)) } + val isLatestVersion = appVersionChecker.isLatestVersion() + reduce { + MyPageHomeUiState.Success( + MyPageHomeUiModel( + user = info.user, + sajuPalja = info.sajuPalja, + appVersionName = appVersionChecker.getCurrentVersionName(), + isLatestVersion = isLatestVersion, + ), + ) + } }.onFailure { throwable -> reduce { MyPageHomeUiState.Fail(throwable) } } diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt index b9fa8b1..1c1563f 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/model/MyPageHomeUiModel.kt @@ -6,4 +6,6 @@ import com.kikidan.domain.model.user.User data class MyPageHomeUiModel( val user: User, val sajuPalja: SajuPalja, + val appVersionName: String, + val isLatestVersion: Boolean = true, ) diff --git a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt index ef6bbdf..693196d 100644 --- a/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt +++ b/feature/mypage/src/main/java/com/kikidan/mypage/home/ui/MyPageHomeScreen.kt @@ -138,13 +138,16 @@ private fun MyPageHomeContent( ) { Text( text = stringResource(R.string.mypage_app_version_label), - style = TodakunTypography.caption1Regular, + style = TodakunTypography.body3Medium, color = TodakunColor.gray400, ) - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(16.dp)) + val versionText = + stringResource(R.string.mypage_app_version_format, model.appVersionName) + + if (model.isLatestVersion) " " + stringResource(R.string.mypage_latest_version) else "" Text( - text = stringResource(R.string.mypage_app_version_value), - style = TodakunTypography.caption1Regular, + text = versionText, + style = TodakunTypography.body3Medium, color = TodakunColor.gray400, ) } @@ -178,6 +181,8 @@ private fun MyPageHomeScreenPreview() { dayPillar = SajuPillar(CheonGan.GYE, JiJi.MYO), hourPillar = SajuPillar(CheonGan.JEONG, JiJi.CHUK), ), + appVersionName = "1.0.0", + isLatestVersion = false, ), ), ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c67c8a..39989b6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ material3AdaptiveNav3 = "1.3.0-rc01" ktlint = "1.8.0" ktlintGradlePlugin = "14.2.0" composeRules = "0.6.2" +playAppUpdate = "2.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -68,6 +69,8 @@ orbit-test = { group = "org.orbit-mvi", name = "orbit-test", version.ref = "orbi # Navigation 3 libraries kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-play-services = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-play-services", version.ref = "kotlinx-coroutines" } +play-app-update-ktx = { group = "com.google.android.play", name = "app-update-ktx", version.ref = "playAppUpdate" } androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }