Skip to content

Conversation

@DongChyeon
Copy link
Member

@DongChyeon DongChyeon commented Sep 16, 2025

Related issue πŸ› 

closed #<issue_number>

μ–΄λ–€ 변경사항이 μžˆμ—ˆλ‚˜μš”?

  • 🐞 BugFix Something isn't working
  • 🎨 Design Markup & styling
  • πŸ“ƒ Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • πŸ”¨ Refactor Code refactoring
  • βš™οΈ Setting Development environment setup
  • βœ… Test Test related (Junit, etc.)

CheckPoint βœ…

PR이 λ‹€μŒ μš”κ΅¬ 사항을 μΆ©μ‘±ν•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”.

  • PR μ»¨λ²€μ…˜μ— 맞게 μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€. (ν•„μˆ˜)
  • mergeν•  브랜치의 μœ„μΉ˜λ₯Ό 확인해 μ£Όμ„Έμš”(main❌/developβ­•) (ν•„μˆ˜)
  • Approve된 PR은 assignerκ°€ λ¨Έμ§€ν•˜κ³ , μˆ˜μ • μš”μ²­μ΄ 온 경우 μˆ˜μ • ν›„ λ‹€μ‹œ pushλ₯Ό ν•©λ‹ˆλ‹€. (ν•„μˆ˜)
  • BugFix의 경우, λ²„κ·Έμ˜ 원인을 νŒŒμ•…ν•˜μ˜€μŠ΅λ‹ˆλ‹€. (선택)

Work Description ✏️

  • μž‘μ—… λ‚΄μš©

Uncompleted Tasks πŸ˜…

  • Task1

To Reviewers πŸ“’

Summary by CodeRabbit

  • New Features

    • ν™ˆ μ—…λ°μ΄νŠΈ μ•ˆλ‚΄ λ°°λ„ˆ μΆ”κ°€(λ‹«κΈ°/λ‹€μ‹œ 보지 μ•ŠκΈ°)
    • 포좘 λ”₯링크 지원(orbitapp://fortune)
    • μ•ŒλžŒ λ°œμƒ ν›„ 당일 1회 포좘 μžλ™ 생성(λ°±κ·ΈλΌμš΄λ“œ μŠ€μΌ€μ€„λ§)
  • UI/UX

    • 포좘 λ‘œλ”© ν™”λ©΄ 개편(μƒˆ μ• λ‹ˆλ©”μ΄μ…˜ 및 동적 μ—°μΆœ)
    • λ‚΄λΉ„κ²Œμ΄μ…˜ λ°” 슀크림 및 μ‹œμŠ€ν…œ λ°” νŒ¨λ”© 반영
    • μ•ŒλžŒ μƒν˜Έμž‘μš©Β·μ˜¨λ³΄λ”©Β·μ•ŒλžŒ νŽΈμ§‘ λ ˆμ΄μ•„μ›ƒ κ°œμ„ 
    • λ―Έμ…˜ 선택 UI κ°œμ„  및 선택 μƒνƒœ ν‘œμ‹œ
  • Improvements

    • β€œν•˜λ£¨ 첫 μ•ŒλžŒ ν•΄μ œβ€ νŒλ³„ 둜직 κ°œμ„ (정확도 ν–₯상)
    • μž¬μ˜ˆμ•½ μ‹œ λΉ„ν™œμ„± μ•ŒλžŒ μ œμ™Έ 처리
    • 이미지 λ‘œλ”© μ„±λŠ₯ 및 라이브러리 μ—…λ°μ΄νŠΈ
    • μ•± 버전 μ—…λ°μ΄νŠΈ: 1.1.3
  • Permissions

    • λ„€νŠΈμ›Œν¬ μƒνƒœ κΆŒν•œ μΆ”κ°€
  • Chores

    • WorkManager/Hilt 톡합 및 μ˜μ‘΄μ„± μ •λΉ„ (Work, Hilt, Coil λ“±)

…-status

[REFACTOR] μš΄μ„Έ 생성 μƒνƒœ κ΄€λ¦¬μš© FortuneCreateStatusFlow λ„μž…
…om-sheet

[FEAT] μ—…λ°μ΄νŠΈ 곡지 λ°”ν…€μ‹œνŠΈ
@DongChyeon DongChyeon self-assigned this Sep 16, 2025
@DongChyeon DongChyeon added the β€οΈβ€πŸ©Ή CHORE μž‘μ€ μˆ˜μ •(νƒ€μž…λ³€μˆ˜, νŒ¨ν‚€μ§€κ΅¬μ‘° λ³€κ²½ λ“±) label Sep 16, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (2 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check βœ… Passed PR 제λͺ© 'Merge release/1.1.3 to develop'은 μ†ŒμŠ€μ™€ λŒ€μƒ 브랜치λ₯Ό λͺ…ν™•νžˆ ν‘œμ‹œν•˜μ—¬ PR의 핡심 λͺ©μ (릴리슀 브랜치 병합)을 μ •ν™•ν•˜κ²Œ μš”μ•½ν•©λ‹ˆλ‹€. κ°„κ²°ν•˜κ³  νŒλ…μ„±μ΄ μ’‹μ•„ νžˆμŠ€ν† λ¦¬ μŠ€μΊ” μ‹œ 무엇을 μˆ˜ν–‰ν–ˆλŠ”μ§€ λΆ„λͺ…νžˆ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ 제λͺ©μ΄ λ³€κ²½λœ μ£Όμš” κΈ°λŠ₯μ΄λ‚˜ 버그 μˆ˜μ •μ„ μ„€λͺ…ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ, λΉ λ₯Έ λ§₯락 νŒŒμ•…μ„ μœ„ν•΄ 더 μ„œμˆ μ μΈ 제λͺ©μ„ ꢌμž₯ν•©λ‹ˆλ‹€.
✨ Finishing touches
  • πŸ“ Generate Docstrings
πŸ§ͺ Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch release/1.1.3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Sep 16, 2025

Codecov Report

❌ Patch coverage is 1.16009% with 426 lines in your changes missing coverage. Please review.
βœ… Project coverage is 4.06%. Comparing base (374369f) to head (700e803).
⚠️ Report is 70 commits behind head on develop.

Files with missing lines Patch % Lines
...e/component/bottomsheet/UpdateNoticeBottomSheet.kt 0.00% 86 Missing ⚠️
...ure/home/src/main/java/com/yapp/home/HomeScreen.kt 0.00% 56 Missing ⚠️
...larm/receivers/AlarmInteractionActivityReceiver.kt 0.00% 47 Missing ⚠️
...m/component/bottomsheet/AlarmMissionBottomSheet.kt 0.00% 45 Missing ⚠️
.../home/src/main/java/com/yapp/home/HomeViewModel.kt 0.00% 41 Missing ⚠️
.../com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt 0.00% 37 Missing ⚠️
...ata/local/datasource/FortuneLocalDataSourceImpl.kt 0.00% 28 Missing ⚠️
...home/component/bottomsheet/AlarmListBottomSheet.kt 0.00% 21 Missing ⚠️
...ain/java/com/yapp/alarm/receivers/AlarmReceiver.kt 0.00% 20 Missing ⚠️
.../main/java/com/yapp/alarm/AndroidAlarmScheduler.kt 0.00% 10 Missing ⚠️
... and 6 more

❌ Your project status has failed because the head coverage (4.06%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             develop    #255      +/-   ##
============================================
- Coverage       4.25%   4.06%   -0.19%     
  Complexity        53      53              
============================================
  Files             50      51       +1     
  Lines           4446    4670     +224     
  Branches         649     689      +40     
============================================
+ Hits             189     190       +1     
- Misses          4247    4470     +223     
  Partials          10      10              
Files with missing lines Coverage Ξ”
.../data/local/datasource/AlarmLocalDataSourceImpl.kt 0.00% <ΓΈ> (ΓΈ)
...om/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt 0.00% <ΓΈ> (ΓΈ)
...e/home/src/main/java/com/yapp/home/HomeContract.kt 0.00% <0.00%> (ΓΈ)
.../main/java/com/yapp/alarm/services/AlarmService.kt 0.00% <0.00%> (ΓΈ)
...yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt 0.00% <0.00%> (ΓΈ)
...p/data/local/datasource/UserLocalDataSourceImpl.kt 0.00% <0.00%> (ΓΈ)
...om/yapp/alarm/receivers/RescheduleAlarmReceiver.kt 0.00% <0.00%> (ΓΈ)
.../yapp/data/repositoryimpl/FortuneRepositoryImpl.kt 42.30% <35.71%> (+0.64%) ⬆️
.../main/java/com/yapp/alarm/AndroidAlarmScheduler.kt 0.00% <0.00%> (ΓΈ)
...ain/java/com/yapp/alarm/receivers/AlarmReceiver.kt 0.00% <0.00%> (ΓΈ)
... and 8 more
πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
core/network/src/main/java/com/yapp/network/di/NetworkModule.kt (2)

41-50: νƒ€μž„μ•„μ›ƒ 300μ΄ˆλŠ” 과도 β€” μ‹€νŒ¨ 감지 μ§€μ—°/λ¦¬μ†ŒμŠ€ 점유 증가

λ³΄νŽΈκ°’μœΌλ‘œλŠ” κ³Όν•©λ‹ˆλ‹€. 일반 APIλŠ” 더 짧게, λŒ€μš©λŸ‰ μ—…/λ‹€μš΄λ‘œλ“œλ§Œ per-call둜 λŠ˜λ¦¬λŠ” 게 μ•ˆμ „ν•©λ‹ˆλ‹€. 총 호좜 μ œν•œμ„ μœ„ν•΄ callTimeout도 κ³ λ €ν•΄ μ£Όμ„Έμš”.

-            .readTimeout(300, TimeUnit.SECONDS)
-            .writeTimeout(300, TimeUnit.SECONDS)
-            .connectTimeout(300, TimeUnit.SECONDS)
+            .connectTimeout(15, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .writeTimeout(30, TimeUnit.SECONDS)
+            .callTimeout(60, TimeUnit.SECONDS)

41-50: κΈ΄κΈ‰: 인증 Interceptor/Authenticator λˆ„λ½ β€” 보호 API μ‹€νŒ¨(λΈ”λ‘œμ»€), μ¦‰μ‹œ 볡ꡬ ν•„μš”

core/network/src/main/java/com/yapp/network/di/NetworkModule.kt의 provideHttpClient(라인 41–49)μ—μ„œ OkHttpClient에 loggingInterceptor만 λ“±λ‘λ˜μ–΄ 있으며, μ €μž₯μ†Œ(.kt/.java) 전체 검색 κ²°κ³Ό addInterceptor/.authenticator 호좜 및 Authorization 토큰 μ£Όμž… 흔적이 λ°œκ²¬λ˜μ§€ μ•ŠμŒ β€” 보호 APIλŠ” Authorization 헀더가 μ—†μœΌλ©΄ 401/μ‹€νŒ¨ λ°œμƒ.
쑰치(ꢌμž₯): provideHttpClient에 인증 Interceptor 및/λ˜λŠ” Authenticator μ˜μ‘΄μ„± μ£Όμž… ν›„ Builder에 적용(예: optional authInterceptor/authenticator νŒŒλΌλ―Έν„° ν›„ addInterceptor/authenticator 호좜) 및 토큰 μ£Όμž…Β·401 μž¬λ°œκΈ‰ 둜직 정상 λ™μž‘ 확인.

feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (1)

134-147: 빈 μ‚¬μš΄λ“œ λͺ©λ‘ μ‹œ IndexOutOfBoundsException μœ„ν—˜.

soundsκ°€ 빈 리슀트면 index 0 μ ‘κ·Όμ—μ„œ ν¬λž˜μ‹œκ°€ λ‚©λ‹ˆλ‹€. μ•ˆμ „ κ°€λ“œλ₯Ό μΆ”κ°€ν•˜μ„Έμš”.

λ‹€μŒμ²˜λŸΌ κΈ°λ³Έ 선택 λ‘œμ§μ„ μ•ˆμ „ν•˜κ²Œ λ°”κΎΈλŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€.

-        alarmUseCase.getAlarmSounds().onSuccess { sounds ->
-            val defaultSoundIndex = sounds.indexOfFirst { it.title == "Homecoming" }.takeIf { it >= 0 } ?: 0
-            val defaultSoundUri = sounds[defaultSoundIndex]
+        alarmUseCase.getAlarmSounds().onSuccess { sounds ->
+            val defaultSound = sounds.firstOrNull { it.title == "Homecoming" } ?: sounds.firstOrNull()
+            if (defaultSound == null) {
+                Log.w("OnboardingViewModel", "No alarm sounds available; skip creating alarm")
+                return@onSuccess
+            }

그리고 μ‚¬μš©λΆ€:

-                soundUri = "${defaultSoundUri.uri}",
+                soundUri = defaultSound.uri,
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt (1)

73-79: 선택 이벀트 λ‘œκΉ…μ΄ λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€ (logEvent λ―Έμ£Όμž…).

OnboardingGenderScreen의 logEvent νŒŒλΌλ―Έν„°κ°€ κΈ°λ³Έκ°’ 빈 λžŒλ‹€μ—¬μ„œ 성별 선택 λ‘œκ·Έκ°€ μ‚¬λΌμ§‘λ‹ˆλ‹€. Routeμ—μ„œ μ£Όμž…ν•˜μ„Έμš”.

λ‹€μŒκ³Ό 같이 μ£Όμž…μ„ ꢌμž₯ν•©λ‹ˆλ‹€.

     OnboardingGenderScreen(
         state = state,
         bottomSheetState = bottomSheetState,
         currentStep = 5,
         totalSteps = 6,
         processAction = viewModel::processAction,
+        logEvent = analyticsHelper::logEvent,
     )
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt (1)

102-105: API 33 μ „μš© registerReceiver μ˜€λ²„λ‘œλ“œ μ‚¬μš©μœΌλ‘œ ν•˜μœ„ OSμ—μ„œ ν¬λž˜μ‹œ λ°œμƒ

RECEIVER_EXPORTED ν”Œλž˜κ·Έλ₯Ό λ°›λŠ” 3-인자 μ˜€λ²„λ‘œλ“œλŠ” Android 13(API 33)+ μ „μš©μž…λ‹ˆλ‹€. ν˜„μž¬ μ½”λ“œ κ·ΈλŒ€λ‘œλ©΄ API 32 μ΄ν•˜μ—μ„œ NoSuchMethodError둜 ν¬λž˜μ‹œκ°€ λ‚©λ‹ˆλ‹€. SDK κ°€λ“œλ‘œ λΆ„κΈ°ν•΄ μ£Όμ„Έμš”.

 private fun registerAlarmInteractionActivityCloseReceiver() {
     val filter = IntentFilter(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE)
-    registerReceiver(broadcastReceiver, filter, RECEIVER_EXPORTED)
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        registerReceiver(broadcastReceiver, filter, RECEIVER_EXPORTED)
+    } else {
+        @Suppress("DEPRECATION")
+        registerReceiver(broadcastReceiver, filter)
+    }
 }
core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt (1)

3-3: 잘λͺ»λœ import둜 컴파일 μ‹€νŒ¨ κ°€λŠ₯μ„±

MissionModeλŠ” com.yapp.domain.model νŒ¨ν‚€μ§€λ‘œ λ³΄μž…λ‹ˆλ‹€. import 경둜λ₯Ό μˆ˜μ •ν•΄ μ£Όμ„Έμš”.

-import com.yapp.domain.MissionMode
+import com.yapp.domain.model.MissionMode
feature/home/src/main/java/com/yapp/home/HomeScreen.kt (1)

295-299: LocalDensity 캑처 λˆ„λ½μœΌλ‘œ 컴파일 μ—λŸ¬ 유발.

μ•„λž˜μ—μ„œ placeable.height.toDp()λ₯Ό ν˜ΈμΆœν•˜μ§€λ§Œ Density μŠ€μ½”ν”„κ°€ μ—†μ–΄ μ»΄νŒŒμΌμ— μ‹€νŒ¨ν•©λ‹ˆλ‹€. LocalDensity.currentλ₯Ό μΊ‘μ²˜ν•΄ μ‚¬μš©ν•΄ μ£Όμ„Έμš”.

     var sheetHalfExpandHeight by remember { mutableStateOf(0.dp) }
 
+    val density = LocalDensity.current
🧹 Nitpick comments (70)
feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt (4)

308-316: ν•˜λ“œμ½”λ”©λœ 20.dp 반볡 μ΅œμ†Œν™”

λ™μΌν•œ μˆ˜ν‰ νŒ¨λ”© 값이 μ—¬λŸ¬ κ³³μ—μ„œ λ°˜λ³΅λ©λ‹ˆλ‹€. 둜컬 μƒμˆ˜λ‘œ μΆ”μΆœν•΄ μœ μ§€λ³΄μˆ˜μ„±μ„ 높이면 μ’‹κ² μŠ΅λ‹ˆλ‹€.

μ•„λž˜μ²˜λŸΌ κ°„λ‹¨νžˆ 정리할 수 μžˆμŠ΅λ‹ˆλ‹€:

@@
-    OrbitBottomSheetLayout(sheetState = bottomSheetState) {
-        Column(
+    OrbitBottomSheetLayout(sheetState = bottomSheetState) {
+        val contentHorizontalPadding = 20.dp
+        Column(
@@
-            AlarmAddEditSelectDaysSection(
-                modifier = Modifier.padding(horizontal = 20.dp),
+            AlarmAddEditSelectDaysSection(
+                modifier = Modifier.padding(horizontal = contentHorizontalPadding),
@@
-            AlarmAddEditSettingsSection(
-                modifier = Modifier.padding(horizontal = 20.dp),
+            AlarmAddEditSettingsSection(
+                modifier = Modifier.padding(horizontal = contentHorizontalPadding),
@@
-                    .padding(
-                        start = 20.dp,
-                        end = 20.dp,
+                    .padding(
+                        start = contentHorizontalPadding,
+                        end = contentHorizontalPadding,
                         bottom = 12.dp,
                     ),

Also applies to: 325-329


286-286: λ„€λΉ„κ²Œμ΄μ…˜ λ°” νŒ¨λ”© 쀑볡 μ—¬μ§€

OrbitBottomSheetLayout이 λ‚΄λΆ€μ—μ„œ navigationBarsPadding()을 μ μš©ν•˜λ―€λ‘œ, μ €μž₯ λ²„νŠΌμ˜ bottom = 12.dp와 합쳐져 일뢀 κΈ°κΈ°(3‑button λ‚΄λΉ„κ²Œμ΄μ…˜ λ“±)μ—μ„œ μ§€λ‚˜μΉ˜κ²Œ 큰 ν•˜λ‹¨ 여백이 생길 수 μžˆμŠ΅λ‹ˆλ‹€. UXμΈ‘ 확인 ν›„ ν•„μš” μ‹œ λ²„νŠΌ μ»¨ν…Œμ΄λ„ˆμ—λŠ” imePadding()만 μΆ”κ°€ν•˜κ±°λ‚˜, ν•˜λ‹¨ 여백을 μ‘°κ±΄λΆ€λ‘œ μ€„μ΄λŠ” 것을 κ³ λ €ν•΄ μ£Όμ„Έμš”.

Also applies to: 321-329


321-324: μ €μž₯ λ²„νŠΌ ν™œμ„±ν™” 쑰건 연동 μ œμ•ˆ

항상 enabled = true둜 λ˜μ–΄ μžˆμ–΄ 비정상 μž…λ ₯/λ―Έλ³€κ²½ μƒνƒœμ—μ„œλ„ μ €μž₯ κ°€λŠ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μœ νš¨μ„±/λ³€κ²½ μ—¬λΆ€ ν”Œλž˜κ·Έκ°€ μžˆλ‹€λ©΄ 연동을 ꢌμž₯ν•©λ‹ˆλ‹€(예: enabled = state.isSaveEnabled).


288-331: μ†Œν˜• ν™”λ©΄/폰트 ν™•λŒ€ν•œ ν™˜κ²½μ—μ„œμ˜ 슀크둀 κ°€λŠ₯μ„± κ²€ν† 

ν˜„μž¬ 루트 Column은 슀크둀이 μ—†μ–΄, λ‚΄μš©μ΄ κΈΈμ–΄μ§€λŠ” μƒνƒœ(μ–Έμ–΄/폰트 μŠ€μΌ€μΌ, μ‹œμŠ€ν…œ 제슀처 μ˜μ—­ ν•©μ‚°)μ—μ„œ ν•˜λ‹¨ λ²„νŠΌμ΄ κ°€λ €μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. μ—¬μœ κ°€ μžˆλ‹€λ©΄ LazyColumn μ „ν™˜ ν˜Ήμ€ μ„Ήμ…˜λ³„ 슀크둀 처리 κ²€ν† λ₯Ό μ œμ•ˆν•©λ‹ˆλ‹€.

feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (1)

326-331: 두 개의 DropdownMenuκ°€ λ™μ‹œμ— 열릴 수 μžˆλŠ” μƒνƒœ 곡간

이제 항상 Compose νŠΈλ¦¬μ— μ‘΄μž¬ν•˜λ―€λ‘œ menuExpanded와 sortDropDownMenuExpandedκ°€ λ™μ‹œμ— trueκ°€ 되면 두 메뉴가 겹쳐 열릴 수 μžˆμŠ΅λ‹ˆλ‹€. 호좜 μΈ‘ λΆˆλ³€μ‹μœΌλ‘œ μƒν˜Έ 배타λ₯Ό 보μž₯ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, 단일 μƒνƒœλ‘œ ν†΅ν•©ν•˜λŠ” 편이 μ•ˆμ „ν•©λ‹ˆλ‹€.

간단 μ œμ•ˆ:

  • 단일 μƒνƒœλ‘œ 톡합: enum class Menu { None, Main, Sort }λ₯Ό μ‚¬μš©ν•˜κ³  expanded = (menu == Main/Sort)둜 μ œμ–΄.
  • ν˜Ήμ€ onClickSortμ—μ„œ λ°˜λ“œμ‹œ menuExpanded=falseλ₯Ό 보μž₯.

ν˜ΈμΆœλΆ€μ—μ„œ 두 λΆˆλ¦°μ„ λ™μ‹œμ— true둜 λ§Œλ“€μ§€ μ•ŠλŠ” 둜직이 μžˆλŠ”μ§€ ν™•μΈν•΄μ£Όμ„Έμš”. μ—†μœΌλ©΄ μœ„ λ¦¬νŒ©ν„°λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

Also applies to: 333-338

data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt (1)

16-17: λ©”μ„œλ“œ 넀이밍 일관성(nit): provide… ν˜•νƒœ ꢌμž₯

Hilt 관둀상 provideXxx둜 λ§žμΆ”λ©΄ 가독성이 μ’‹μ•„μ§‘λ‹ˆλ‹€.

-    fun providesApiService(retrofit: Retrofit): ApiService =
+    fun provideApiService(retrofit: Retrofit): ApiService =
core/network/src/main/java/com/yapp/network/di/NetworkModule.kt (3)

54-63: kotlinx.serialization 컨버터 Opt‑in ν•„μš” κ°€λŠ₯μ„±

ν”„λ‘œμ νŠΈ 섀정에 따라 asConverterFactory μ‚¬μš© μ‹œ ExperimentalSerializationApi Opt‑in이 ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 컴파일 κ²½κ³ /μ—λŸ¬κ°€ 보인닀면 μ•„λž˜μ²˜λŸΌ λͺ…μ‹œ Opt‑in ν•΄ μ£Όμ„Έμš”.

-    fun provideRetrofit(
+    @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
+    fun provideRetrofit(
         okHttpClient: OkHttpClient,
         buildConfigFieldProvider: BuildConfigFieldProvider,
         json: Json,
     ): Retrofit =

54-63: 비‑JSON/빈 응닡 λŒ€λΉ„: Scalars 컨버터 선등둝 ꢌμž₯

μ„œλ²„κ°€ text/plain λ˜λŠ” 빈 λ°”λ””(204/200 without body)λ₯Ό λ°˜ν™˜ν•˜λŠ” μ—”λ“œν¬μΈνŠΈκ°€ μžˆλ‹€λ©΄ JSON μ»¨λ²„ν„°λ§ŒμœΌλ‘œλŠ” μ‹€νŒ¨ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν•„μš” μ‹œ ScalarsConverterFactoryλ₯Ό JSON보닀 λ¨Όμ € μΆ”κ°€ν•΄ μ£Όμ„Έμš”.

         Retrofit.Builder()
-            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
+            .addConverterFactory(retrofit2.converter.scalars.ScalarsConverterFactory.create())
+            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
             .baseUrl(buildConfigFieldProvider.get().baseUrl)
             .client(okHttpClient)
             .build()

54-63: baseUrl 트레일링 μŠ¬λž˜μ‹œ 보μž₯

Retrofit은 baseUrl이 λ°˜λ“œμ‹œ '/'둜 λλ‚˜μ•Ό ν•©λ‹ˆλ‹€. BuildConfig 값이 보μž₯λ˜μ§€ μ•ŠμœΌλ©΄ λŸ°νƒ€μž„ μ˜ˆμ™Έκ°€ λ‚©λ‹ˆλ‹€. λ°©μ–΄ μ½”λ“œ μΆ”κ°€λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

-            .baseUrl(buildConfigFieldProvider.get().baseUrl)
+            .baseUrl(
+                buildConfigFieldProvider.get().baseUrl.let {
+                    if (it.endsWith("/")) it else "$it/"
+                }
+            )
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt (2)

168-168: ν•˜λ“œμ½”λ”©λœ λ²„νŠΌ 라벨 i18n 처리 ν•„μš”.

λ‹€κ΅­μ–΄ 및 μ ‘κ·Όμ„± 일관성을 μœ„ν•΄ stringResource μ‚¬μš©μ„ ꢌμž₯ν•©λ‹ˆλ‹€.

λ¦¬μ†ŒμŠ€ ν‚€λŠ” ν”„λ‘œμ νŠΈ 상황에 맞게 κ΅μ²΄ν•˜μ„Έμš”.

-            buttonLabel = "λ‹€μŒ",
+            buttonLabel = stringResource(id = R.string.common_button_next),

190-206: UI 라벨을 μƒνƒœ κ°’μœΌλ‘œ 직접 μ €μž₯/λΉ„κ΅ν•˜λŠ” νŒ¨ν„΄μ€ i18n/도메인 일관성에 μ·¨μ•½ν•©λ‹ˆλ‹€.

ν‘œμ‹œ λ¬Έμžμ—΄(남성/μ—¬μ„±) λŒ€μ‹  도메인 μ½”λ“œ(예: "MALE"/"FEMALE" λ˜λŠ” enum)λ₯Ό μƒνƒœμ— λ³΄κ΄€ν•˜κ³ , 라벨은 stringResource둜 λ§€ν•‘ν•˜μ„Έμš”.

κ΅­μ œν™”μ™€ μ„œλ²„ μŠ€ν‚€λ§ˆ 변경에 κ°•ν•œ ν˜•νƒœλ‘œ λ‹€μŒκ³Ό 같이 κ°œμ„ μ„ μ œμ•ˆν•©λ‹ˆλ‹€.

-                    listOf("남성", "μ—¬μ„±").forEach { gender ->
+                    listOf(
+                        "MALE" to stringResource(R.string.gender_male),
+                        "FEMALE" to stringResource(R.string.gender_female),
+                    ).forEach { (genderCode, genderLabel) ->
                         Box(modifier = Modifier.weight(1f)) {
                             OrbitGenderToggle(
-                                label = gender,
-                                isSelected = state.selectedGender == gender,
+                                label = genderLabel,
+                                isSelected = state.selectedGender == genderCode,
                                 onToggle = {
                                     logEvent(
                                         AnalyticsEvent(
                                             type = "onboarding_gender_select",
                                             properties = mapOf(
-                                                AnalyticsEvent.OnboardingPropertiesKeys.GENDER to gender,
+                                                AnalyticsEvent.OnboardingPropertiesKeys.GENDER to genderCode,
                                             ),
                                         ),
                                     )
-                                    processAction(OnboardingContract.Action.UpdateGender(gender))
+                                    processAction(OnboardingContract.Action.UpdateGender(genderCode))
                                 },
                             )
                         }
                     }

λ¦¬μ†ŒμŠ€ ν‚€(R.string.gender_male/female)λŠ” μ‹€μ œ ν”„λ‘œμ νŠΈ ν‚€λ‘œ ꡐ체 λ°”λžλ‹ˆλ‹€.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt (1)

17-18: Boolean? β†’ Boolean으둜 λ‹¨μˆœν™”ν•˜κ³  null κ²Œμ΄νŒ… 제거 μ œμ•ˆ

초기 λ‘œλ”©μ€ initialLoading둜 이미 κ°€λ €μ§€λ―€λ‘œ shouldShowMissionStart의 삼쀑 μƒνƒœ(null/true/false)λŠ” λΆˆν•„μš”ν•©λ‹ˆλ‹€. λΆˆλ³€(Boolean)둜 μ’ν˜€ νƒ€μž… μ•ˆμ •μ„±κ³Ό λΆ„κΈ° λ‹¨μˆœν™”λ₯Ό κ°€μ Έκ°€λ©΄ μ’‹κ² μŠ΅λ‹ˆλ‹€.

μ•„λž˜μ²˜λŸΌ λ³€κ²½ μ‹œ, ν™”λ©΄ μͺ½μ˜ isFirstMission != null 뢄기도 제거 κ°€λŠ₯ν•©λ‹ˆλ‹€.

-        val shouldShowMissionStart: Boolean? = null,
+        val shouldShowMissionStart: Boolean = false,

ν™”λ©΄ 반영(λ‹€λ₯Έ 파일)도 ν•¨κ»˜ ν•„μš”ν•©λ‹ˆλ‹€:

-    isFirstMission: Boolean?,
+    shouldShowMissionStart: Boolean,

및 λ²„νŠΌ λ Œλ”λ§μ—μ„œ null λΆ„κΈ° 제거.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (5)

115-126: νŒŒλΌλ―Έν„° λͺ…/νƒ€μž…μ„ μƒνƒœμ™€ μΌμΉ˜ν•˜λ„λ‘ 정리

컴포저블 νŒŒλΌλ―Έν„° isFirstMission: Boolean?λŠ” μƒνƒœλͺ…(shouldShowMissionStart)κ³Ό μ˜λ―Έκ°€ λ‹€λ₯΄κ³  nullableμž…λ‹ˆλ‹€. 동일 λͺ…μΉ­Β·λΆˆλ³€(Boolean)으둜 μ •λ¦¬ν•˜λ©΄ 이해와 μ‚¬μš©μ΄ μ‰¬μ›Œμ§‘λ‹ˆλ‹€.

-private fun AlarmActionContent(
+private fun AlarmActionContent(
     isAm: Boolean,
     hour: Int,
     minute: Int,
     todayDate: String,
     snoozeEnabled: Boolean,
     snoozeInterval: Int,
     snoozeCount: Int,
-    isFirstMission: Boolean?,
+    shouldShowMissionStart: Boolean,
     onSnoozeClick: () -> Unit,
     onDismissClick: () -> Unit,
 )

ν˜ΈμΆœλΆ€λ„ ν•¨κ»˜:

-            isFirstMission = state.shouldShowMissionStart,
+            shouldShowMissionStart = state.shouldShowMissionStart,

170-189: λ²„νŠΌ λ Œλ”λ§μ˜ null κ²Œμ΄νŒ… 제거둜 UI λ‹¨μˆœν™”

initialLoading 화면이 μžˆμœΌλ―€λ‘œ μ—¬κΈ°μ„œμ˜ null λΆ„κΈ°λŠ” λΆˆν•„μš”ν•©λ‹ˆλ‹€. 항상 λ²„νŠΌμ„ λ Œλ”λ§ν•˜κ³  라벨만 λΆ„κΈ°ν•˜μ„Έμš”.

-        if (isFirstMission != null) {
-            OrbitButton(
-                label = if (isFirstMission) {
+        OrbitButton(
+            label = if (shouldShowMissionStart) {
                 stringResource(id = R.string.alarm_off_mission_start_btn)
             } else {
                 stringResource(id = R.string.alarm_off_btn)
-            },
-            enabled = true,
-            modifier = Modifier
-                .padding(
-                    start = 40.dp,
-                    end = 40.dp,
-                    bottom = 48.dp,
-                )
-                .height(62.dp),
-            onClick = onDismissClick,
-            )
-        } else {
-            Spacer(modifier = Modifier.height(62.dp))
-        }
+            },
+            enabled = true,
+            modifier = Modifier
+                .padding(start = 40.dp, end = 40.dp, bottom = 48.dp)
+                .height(62.dp),
+            onClick = onDismissClick,
+        )

150-154: contentDescription ν•˜λ“œμ½”λ”© β†’ stringResource둜 i18n

μ ‘κ·Όμ„± λ¬Έμžμ—΄μ€ λ¦¬μ†ŒμŠ€λ‘œ λΆ„λ¦¬ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. λ¦¬μ†ŒμŠ€ μΆ”κ°€ ν›„ ꡐ체λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

예:

-            contentDescription = "Alarm Action Character",
+            contentDescription = stringResource(id = R.string.alarm_action_character_cd),

208-213: "μ˜€μ „/μ˜€ν›„" ν•˜λ“œμ½”λ”© 제거

λ‹€κ΅­μ–΄ λŒ€μ‘μ„ μœ„ν•΄ λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄ λŒ€μ‹  stringResource μ‚¬μš©μ„ ꢌμž₯ν•©λ‹ˆλ‹€.

예:

text = stringResource(if (isAm) R.string.am else R.string.pm)

299-313: 프리뷰 μΌ€μ΄μŠ€ 보강(λ―Έμ…˜ 유무 2μ’…)

λ―Έμ…˜ μ‹œμž‘/일반 μ’…λ£Œ λ²„νŠΌ 라벨을 λͺ¨λ‘ 검증할 수 μžˆλ„λ‘ 프리뷰λ₯Ό 2개둜 λ‚˜λˆ„λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt (3)

30-36: 초기 Intent μˆœμ„œ OK, λ‹€λ§Œ μ΄ˆκΈ°λ‘œλ”© ν•΄μ œ νƒ€μ΄λ°λ§Œ 일관화 μ œμ•ˆ

fetchShouldShowMissionStart() β†’ initializeAlarmState() β†’ startClock() μˆœμ„œλŠ” ν•©λ¦¬μ μž…λ‹ˆλ‹€. initialLoading = falseλŠ” λ§€μ΄ˆκ°€ μ•„λ‹ˆλΌ 첫 ν‹±μ—μ„œλ§Œ 내리도둝 ν•˜λ©΄ λΆˆν•„μš”ν•œ μƒνƒœ 갱신을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.


64-82: λ¬΄ν•œ λ£¨ν”„λŠ” μ·¨μ†Œ μΉœν™”μ μœΌλ‘œ

μ·¨μ†Œ μ‹œκ·Έλ„ 인지 λͺ…확성을 μœ„ν•΄ while (true) λŒ€μ‹  coroutineContext.isActive μ‚¬μš©μ„ ꢌμž₯ν•©λ‹ˆλ‹€.

+import kotlinx.coroutines.isActive
@@
-    private fun startClock() = intent {
-        while (true) {
+    private fun startClock() = intent {
+        while (coroutineContext.isActive) {
             val now = LocalTime.now()
             ...
             delay(1000L)
         }
     }

84-98: Snooze 닀쀑 νƒ­ κ°€λ“œ

연속 νƒ­ μ‹œ μ‚¬μ΄λ“œμ΄νŽ™νŠΈ 쀑볡 λ‚΄λΉ„κ²Œμ΄μ…˜ κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€. μΌμ‹œμ  λ””μŠ€μ—μ΄λΈ” ν”Œλž˜κ·Έλ₯Ό μƒνƒœμ— μΆ”κ°€ν•˜κ±°λ‚˜, 처리 쀑 κ°€λ“œλ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

feature/home/src/main/AndroidManifest.xml (1)

3-3: κΆŒν•œ 쀑볡 μ„ μ–Έ 정리 μ œμ•ˆ

ACCESS_NETWORK_STATEλŠ” 보톡 μ•± λͺ¨λ“ˆ λ§€λ‹ˆνŽ˜μŠ€νŠΈμ—μ„œλ§Œ μ„ μ–Έν•©λ‹ˆλ‹€. ν”Όμ²˜ λͺ¨λ“ˆ 선언은 병합 μ‹œ 쀑볡이 되고, 동적/μ˜¨λ””λ§¨λ“œ λͺ¨λ“ˆμΈ 경우 λΆˆν•„μš” κΆŒν•œ λ…ΈμΆœμ΄ 될 수 μžˆμŠ΅λ‹ˆλ‹€. μ•± λ§€λ‹ˆνŽ˜μŠ€νŠΈμ—λ§Œ μœ μ§€ν•˜κ³  μ—¬κΈ°μ„œλŠ” μ œκ±°ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

적용 diff:

-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <!-- moved to app/src/main/AndroidManifest.xml -->
core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt (1)

3-5: μŠ€μΌ€μ€„λ§ κ°€μ‹œμ„± ν–₯상을 μœ„ν•œ λ°˜ν™˜κ°’/λ¬Έμ„œν™” μ œμ•ˆ

호좜 μΈ‘μ—μ„œ β€œνμž‰ 성곡/μŠ€ν‚΅β€ μ—¬λΆ€λ‚˜ νŠΈλž˜ν‚Ή IDκ°€ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Boolean(성곡/μŠ€ν‚΅) λ°˜ν™˜ λ˜λŠ” KDoc둜 λ™μž‘ 보μž₯을 λͺ…μ‹œν•˜λŠ” κ°œμ„ μ„ μ œμ•ˆν•©λ‹ˆλ‹€.

μ˜ˆμ‹œ diff:

-interface PostFortuneTaskScheduler {
-    fun enqueueOnceForToday()
-}
+/**
+ * 였늘 λ‚ μ§œ κΈ°μ€€μœΌλ‘œ 1회만 νμž‰ν•©λ‹ˆλ‹€(쀑볡 λ°©μ§€).
+ * @return trueλ©΄ μƒˆλ‘œ νμž‰λ¨, falseλ©΄ 이미 νμž‰λ˜μ–΄ μŠ€ν‚΅λ¨.
+ */
+interface PostFortuneTaskScheduler {
+    fun enqueueOnceForToday(): Boolean
+}
core/alarm/src/main/java/com/yapp/alarm/AndroidAlarmScheduler.kt (1)

20-27: 둜그 μ˜€λ²„ν—€λ“œ 절감(μŠ€νƒνŠΈλ ˆμ΄μŠ€/릴리슀 λΉŒλ“œ 차단)

Throwable μŠ€νƒνŠΈλ ˆμ΄μŠ€λŠ” λΉ„μš©μ΄ ν½λ‹ˆλ‹€. 디버그/둜거 κ°€λ“œλ‘œ μ œν•œν•˜κ³  μŠ€νƒνŠΈλ ˆμ΄μŠ€λŠ” μ œκ±°ν•˜μ„Έμš”.

ꢌμž₯ diff:

-    private fun logSchedule(tag: String, alarm: Alarm, triggerMillis: Long, extra: String = "") {
-        Log.d("ScheduleTrace", "scheduleAlarm Called", Throwable())
-        Log.d(
-            "AlarmSchedule",
-            "[$tag] id=${alarm.id}, repeatDays=${alarm.repeatDays}, " +
-                "time=${java.time.Instant.ofEpochMilli(triggerMillis)} $extra",
-        )
-    }
+    private fun logSchedule(tag: String, alarm: Alarm, triggerMillis: Long, extra: String = "") {
+        if (!BuildConfig.DEBUG && !Log.isLoggable("AlarmSchedule", Log.DEBUG)) return
+        Log.d(
+            "AlarmSchedule",
+            "[$tag] id=${alarm.id}, repeatDays=${alarm.repeatDays}, " +
+                "time=${java.time.Instant.ofEpochMilli(triggerMillis)} $extra"
+        )
+    }
core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt (1)

16-26: μž¬μ‚¬μš©μ„± κ°œμ„ : 색상/λͺ¨λ””νŒŒμ΄μ–΄ νŒŒλΌλ―Έν„°ν™” μ œμ•ˆ

ν…Œλ§ˆ 슀크림 컬러 μ‚¬μš©μ΄λ‚˜ zIndex 쑰정이 ν•„μš”ν•  수 μžˆμ–΄ νŒŒλΌλ―Έν„°ν™”ν•˜λ©΄ ν™œμš©λ„κ°€ μ˜¬λΌκ°‘λ‹ˆλ‹€.

μ˜ˆμ‹œ diff:

-@Composable
-fun BoxScope.NavigationBarScrim() {
-    Box(
-        modifier = Modifier
-            .align(Alignment.BottomCenter)
-            .fillMaxWidth()
-            .windowInsetsBottomHeight(WindowInsets.navigationBars)
-            .background(Color.Black)
-            .zIndex(1f),
-    )
-}
+@Composable
+fun BoxScope.NavigationBarScrim(
+    modifier: Modifier = Modifier,
+    color: Color = Color.Black,
+    zIndex: Float = 1f,
+) {
+    Box(
+        modifier = modifier
+            .align(Alignment.BottomCenter)
+            .fillMaxWidth()
+            .windowInsetsBottomHeight(WindowInsets.navigationBars)
+            .background(color)
+            .zIndex(zIndex),
+    )
+}
feature/home/src/main/res/values/strings.xml (2)

51-53: '선택됨' 라벨의 μ‚¬μš© λ§₯락을 a11y/UX κ΄€μ μ—μ„œ 확인해 μ£Όμ„Έμš”.

ν•™μŠ΅ λ©”λͺ¨μƒ(selectedMissionType/CountλŠ” λ‚΄λΆ€ μƒνƒœ)κ³Ό 같이 '선택됨'이 κ°€μ‹œ ν…μŠ€νŠΈλ‘œ λ…ΈμΆœλ˜λ©΄ μ €μž₯/ν™•μ • μƒνƒœλ‘œ 였인될 수 μžˆμŠ΅λ‹ˆλ‹€. μŠ€ν¬λ¦°λ¦¬λ”μš© contentDescription둜만 μ“°μ΄κ±°λ‚˜, UIμ—μ„œλŠ” 체크/선택 μƒνƒœκ°€ λͺ…ν™•νžˆ μ „λ‹¬λ˜λ„λ‘ 확인 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.


128-129: 'λ‹€μ‹œ 보지 μ•ŠκΈ°' λ™μž‘ νƒ€μž…(μ²΄ν¬λ°•μŠ€ vs λ²„νŠΌ) λͺ…ν™•ν™” 및 ν…μŠ€νŠΈ 일관성 확인

ν•΄λ‹Ή λ¬Έμžμ—΄μ΄ ν† κΈ€(μ²΄ν¬λ°•μŠ€)인지, 1νšŒμ„± λ²„νŠΌμΈμ§€μ— 따라 μ ‘κ·Όμ„± λ ˆμ΄λΈ”/μƒνƒœ μ•ˆλ‚΄κ°€ λ‹¬λΌμ§‘λ‹ˆλ‹€. μœ„μ ― νƒ€μž…κ³Ό μΌμΉ˜ν•˜λ„λ‘ 보쑰 ν…μŠ€νŠΈ(예: μ„€λͺ…/μ ‘κ·Όμ„± λ ˆμ΄λΈ”)λ₯Ό μΆ”κ°€ν• μ§€ κ²€ν† ν•΄ μ£Όμ„Έμš”.

app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt (1)

11-18: @nAmed λŒ€μ‹  νƒ€μž… μ„Έμ΄ν”„ν•œ @qualifier μ‚¬μš© μ œμ•ˆ

λ¬Έμžμ—΄ QualifierλŠ” 좩돌/μ˜€νƒ€ λ¦¬μŠ€ν¬κ°€ μžˆμŠ΅λ‹ˆλ‹€. μ „μš© @qualifierλ₯Ό λ„μž…ν•΄ νƒ€μž… μ„Έμ΄ν”„ν•˜κ²Œ 관리해 μ£Όμ„Έμš”.

 package com.yapp.orbit.di
@@
 import dagger.hilt.components.SingletonComponent
-import javax.inject.Named
 import javax.inject.Singleton
+import javax.inject.Qualifier
@@
-@Module
-@InstallIn(SingletonComponent::class)
-object AppVersionModule {
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class AppVersion
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppVersionModule {
     @Provides
     @Singleton
-    @Named("appVersion")
-    fun provideAppVersion(): String = BuildConfig.VERSION_NAME
+    @AppVersion
+    fun provideAppVersion(): String = BuildConfig.VERSION_NAME
 }
core/designsystem/src/main/res/raw/fortune_loading.json (1)

1-1: Lottie 원본 λΌμ΄μ„ μŠ€/크기 검증 ν•„μš”

  • λŒ€μš©λŸ‰ base64 WebP 에셋 인라인은 APK/λ©”λͺ¨λ¦¬ ν’‹ν”„λ¦°νŠΈλ₯Ό λŠ˜λ¦½λ‹ˆλ‹€. κ°€λŠ₯ν•˜λ©΄ 벑터화/에셋 뢄리/압좕을 κ²€ν† ν•˜μ„Έμš”.
  • 원본 λΌμ΄μ„ μŠ€(상업적 이용/λ³€κ²½ ν—ˆμš© μ—¬λΆ€)와 귀속 ν‘œκΈ°λ₯Ό 확인해 μ£Όμ„Έμš”.
domain/src/main/java/com/yapp/domain/model/AlarmDay.kt (1)

16-23: μ–‘λ°©ν–₯ λ§€ν•‘ λ‘œμ§μ€ λ§žμŠ΅λ‹ˆλ‹€λ§Œ, enum μˆœμ„œ μ˜μ‘΄μ„± 제거 ꢌμž₯

ν˜„μž¬ κ΅¬ν˜„μ€ entries/ordinal에 μ˜μ‘΄ν•©λ‹ˆλ‹€. enum μˆœμ„œ 변경에 μ·¨μ•½ν•˜λ―€λ‘œ λͺ…μ‹œμ  λ§€ν•‘μœΌλ‘œ λ°”κΎΈλŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

-fun AlarmDay.toDayOfWeek(): DayOfWeek {
-    return DayOfWeek.of(((this.ordinal + 6) % 7) + 1)
-}
+fun AlarmDay.toDayOfWeek(): DayOfWeek = when (this) {
+    AlarmDay.SUN -> DayOfWeek.SUNDAY
+    AlarmDay.MON -> DayOfWeek.MONDAY
+    AlarmDay.TUE -> DayOfWeek.TUESDAY
+    AlarmDay.WED -> DayOfWeek.WEDNESDAY
+    AlarmDay.THU -> DayOfWeek.THURSDAY
+    AlarmDay.FRI -> DayOfWeek.FRIDAY
+    AlarmDay.SAT -> DayOfWeek.SATURDAY
+}
 
-fun DayOfWeek.toAlarmDay(): AlarmDay {
-    val index = (this.value % 7)
-    return AlarmDay.entries[index]
-}
+fun DayOfWeek.toAlarmDay(): AlarmDay = when (this) {
+    DayOfWeek.SUNDAY -> AlarmDay.SUN
+    DayOfWeek.MONDAY -> AlarmDay.MON
+    DayOfWeek.TUESDAY -> AlarmDay.TUE
+    DayOfWeek.WEDNESDAY -> AlarmDay.WED
+    DayOfWeek.THURSDAY -> AlarmDay.THU
+    DayOfWeek.FRIDAY -> AlarmDay.FRI
+    DayOfWeek.SATURDAY -> AlarmDay.SAT
+}
data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSource.kt (1)

9-10: Epoch λ‹¨μœ„/νƒ€μž„μ‘΄ λͺ…μ„Έ ν•„μš”

  • updateNoticeLastShownDateEpochFlow의 λ‹¨μœ„(초/λ°€λ¦¬μ΄ˆ)와 κΈ°μ€€(UTC/local midnight) λͺ…μ‹œκ°€ ν•„μš”ν•©λ‹ˆλ‹€. λ‚ μ§œ 비ꡐ 둜직과 뢈일치 μ‹œ μ˜€λ™μž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • markUpdateNoticeShownTodayκ°€ μ–΄λ–€ κΈ°μ€€ μ‹œκ°μ„ μ €μž₯ν•˜λŠ”μ§€ KDoc에 λͺ…ν™•νžˆ 남겨 μ£Όμ„Έμš”.

Also applies to: 15-16

app/src/main/java/com/yapp/orbit/OrbitApplication.kt (1)

11-11: 빈 κΈ°λ³Έ μƒμ„±μž 제거 κ°€λŠ₯

static analysis 도ꡬ가 μ§€μ ν•œ κ²ƒμ²˜λŸΌ 빈 κΈ°λ³Έ μƒμ„±μž ()λŠ” μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

-class OrbitApplication() : Application(), Configuration.Provider {
+class OrbitApplication : Application(), Configuration.Provider {
app/src/main/AndroidManifest.xml (1)

5-5: ACCESS_NETWORK_STATE κΆŒν•œ 쀑볡 μ„ μ–Έ

android.permission.ACCESS_NETWORK_STATE κΆŒν•œμ΄ 5번과 14번 라인에 쀑볡 μ„ μ–Έλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

14번 라인의 μ€‘λ³΅λœ κΆŒν•œ 선언을 μ œκ±°ν•˜μ„Έμš”:

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

Also applies to: 14-14

core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt (1)

114-119: 둜컬 ν•¨μˆ˜ μ„ μ–Έ μœ„μΉ˜ κ°œμ„  ν•„μš”

ringsToday() ν•¨μˆ˜κ°€ 코루틴 μŠ€μ½”ν”„ 내뢀에 μ„ μ–Έλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 이 ν•¨μˆ˜λŠ” μƒνƒœλ₯Ό λ³€κ²½ν•˜μ§€ μ•Šκ³  순수 ν•¨μˆ˜μ΄λ―€λ‘œ 클래슀 λ ˆλ²¨μ΄λ‚˜ 상단에 μ„ μ–Έν•˜λŠ” 것이 더 μ μ ˆν•©λ‹ˆλ‹€.

ν•¨μˆ˜λ₯Ό 클래슀의 private λ©”μ„œλ“œλ‘œ μ΄λ™ν•˜κ±°λ‚˜ companion object에 μΆ”κ°€ν•˜μ„Έμš”:

-                fun Alarm.ringsToday(): Boolean {
-                    if (repeatDays == 0) return true
-
-                    val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay()
-                    return (repeatDays and todayAlarmDay.bitValue) != 0
-                }

클래슀 λ ˆλ²¨μ— μΆ”κ°€:

private fun Alarm.ringsToday(): Boolean {
    if (repeatDays == 0) return true
    
    val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay()
    return (repeatDays and todayAlarmDay.bitValue) != 0
}
domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt (2)

20-20: 이름 뢈일치 κ°€λŠ₯μ„±: markFortuneAsFailed vs markFortuneFailed

datastore(UserPreferences) μͺ½ κ΅¬ν˜„μ€ markFortuneFailed둜 λ³΄μž…λ‹ˆλ‹€. λ ˆμ΄μ–΄ κ°„ λͺ…λͺ… 톡일을 ꢌμž₯ν•©λ‹ˆλ‹€. 도메인 λ©”μ„œλ“œλ₯Ό markFortuneFailed둜 λ§žμΆ”κ±°λ‚˜ 데이터 계측을 AsFailed둜 맞좰 μ£Όμ„Έμš”.

-    suspend fun markFortuneAsFailed()
+    suspend fun markFortuneFailed()

9-16: 확인 μ™„λ£Œ β€” 도메인 API 변경이 μ „νŒŒλ¨; KDoc λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ κ°€μ΄λ“œ μΆ”κ°€ ꢌμž₯

검증 κ²°κ³Ό: 이전 심볼(fortuneDateFlow λ“±)은 μ½”λ“œλ² μ΄μŠ€μ—μ„œ λ°œκ²¬λ˜μ§€ μ•Šμ•˜κ³ , μƒˆ 심볼(fortuneDateEpochFlow, hasUnseenFortuneFlow, shouldShowFortuneToolTipFlow, isFirstAlarmDismissedTodayFlow, fortuneCreateStatusFlow)이 domain/data/feature λͺ¨λ“ˆ μ „μ—­μ—μ„œ μ‚¬μš©λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.
확인 파일(예): domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt, data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt, data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt, core/datastore/UserPreferences.kt, feature/* (Home/Mission/Fortune/Alarm).
쑰치: 각 Flow의 의미(ν•˜λ£¨/epoch κΈ°μ€€, reset 쑰건)와 mark* 계열 λ©”μ„œλ“œμ˜ μƒνƒœ 전이λ₯Ό κ°„λ‹¨ν•œ KDoc으둜 λ¬Έμ„œν™”ν•˜μ„Έμš”.

core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (2)

44-45: todayEpoch ν…ŒμŠ€νŠΈ κ°€λŠ₯μ„±/κ²°μ •μ„± κ°œμ„  μ œμ•ˆ

둜컬 μ‹œκ°„λŒ€/μžμ • 경계 이슈 ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ Clock μ£Όμž…(λ˜λŠ” provider ν•¨μˆ˜ μ£Όμž…)을 ꢌμž₯ν•©λ‹ˆλ‹€.

예: μƒμ„±μžμ— Clock μ£Όμž… ν›„ LocalDate.now(clock).toEpochDay() μ‚¬μš©.


108-116: FIRST_ALARM_DISMISSED_TODAY ν”Œλž˜κ·Έ 정리(μ˜΅μ…˜)

ν”Œλ‘œμš°μ—μ„œ λ‚ μ§œ λΉ„κ΅λ‘œ μ•ˆμ „ν•˜μ§€λ§Œ, 데이터 청결을 μœ„ν•΄ λ‚ μ§œκ°€ λ°”λ€Œλ©΄ ν”Œλž˜κ·Έλ₯Ό false둜 μž¬μ„€μ •ν•˜λŠ” μœ μ§€λ³΄μˆ˜μš© 정리 λ‘œμ§μ„ κ³ λ €ν•΄λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt (1)

52-65: λ„€λΉ„κ²Œμ΄μ…˜ λ°” νŒ¨λ”© + 슀크림 μ‘°ν•© μ‚¬μš© μ‹œ 이쀑 νŒ¨λ”© μ—¬λΆ€ 확인 μš”μ²­

NavHost에 navigationBarsPadding을 μ£Όκ³  λ°”λ‹₯에 μŠ€ν¬λ¦Όμ„ κΉ”μ•˜μœΌλ‹ˆ λŒ€λΆ€λΆ„ OKμž…λ‹ˆλ‹€. λ‹€λ§Œ λ‚΄λΆ€ 화면듀이 λ³„λ„λ‘œ navigationBarsPadding/WindowInsetsλ₯Ό μ²˜λ¦¬ν•˜κ³  μžˆλ‹€λ©΄ ν•˜λ‹¨ 여백이 κ³Όλ‹€ν•΄μ§ˆ 수 μžˆμ–΄ μ‹€μ œ 단말(제슀처/3λ²„νŠΌ λ‚΄λΉ„ λͺ¨λ‘)μ—μ„œ 확인 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€. λ˜ν•œ Box에 크기 μ œμ•½μ΄ λͺ¨ν˜Έν•œ 경우λ₯Ό λ§‰μœΌλ €λ©΄ fillMaxSizeλ₯Ό λΆ€μ—¬ν•˜λŠ” 것도 κ³ λ €ν•΄ μ£Όμ„Έμš”.

core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt (1)

8-11: λ¬Έμžμ—΄ λŒ€μ‹  νƒ€μž… 세이프 enum μ‚¬μš© κ³ λ €

missionMode: String λŒ€μ‹  MissionMode 자체λ₯Ό μ‚¬μš©ν•˜λ©΄ μ˜€νƒ€/μŠ€νŽ™ 뢈일치λ₯Ό 컴파일 νƒ€μž„μ— 차단할 수 μžˆμŠ΅λ‹ˆλ‹€(ν•„μš” μ‹œ @serializable enum). λ„€λΉ„κ²Œμ΄μ…˜ 인코딩/λ””μ½”λ”© 영ν–₯ λ²”μœ„λ₯Ό κ°μ•ˆν•΄ 후속 λ¦¬νŒ©ν„°λ‘œ κ²€ν† ν•΄ μ£Όμ„Έμš”.

feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt (1)

43-61: νŽ˜μ΄μ§€ 인덱슀 λ§€μ§λ„˜λ²„ 정리 μ œμ•ˆ

in 1..4, 5 -> λ“± ν•˜λ“œμ½”λ”©λœ μΈλ±μŠ€λŠ” μœ μ§€λ³΄μˆ˜ μ‹œ 였λ₯˜λ₯Ό μœ λ°œν•©λ‹ˆλ‹€. μƒμˆ˜/νŒŒμƒ κ°’μœΌλ‘œ μ€‘μ•™μ§‘μ€‘ν™”ν•˜κ±°λ‚˜ state 기반으둜 κ³„μ‚°ν•˜λ„λ‘ 정리해 μ£Όμ„Έμš”.

feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt (1)

23-27: λ”₯링크 URI νŒ¨ν„΄ μƒμˆ˜ν™”

"orbitapp://fortune" λ¬Έμžμ—΄μ„ μƒμˆ˜λ‘œ 뢄리해 μž¬μ‚¬μš©ν•˜λ©΄ μ˜€νƒˆμž/쀑볡 관리가 μˆ˜μ›”ν•©λ‹ˆλ‹€.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (1)

47-49: Clock μ£Όμž…μœΌλ‘œ ν…ŒμŠ€νŠΈ μš©μ΄μ„±/일관성 ν–₯상 μ œμ•ˆ

LocalDate.now() λŒ€μ‹  Clock을 DI λ°›μ•„ LocalDate.now(clock)을 μ“°λ©΄ μ˜€ν”„λΌμΈ/νƒ€μž„μ‘΄/ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€μ—μ„œ μ•ˆμ •μ μž…λ‹ˆλ‹€.

// μ˜ˆμ‹œ
class AlarmSnoozeTimerViewModel @Inject constructor(
    private val clock: Clock,
    ...
) : ViewModel(), ... {
    ...
    val todayDate = LocalDate.now(clock).toEpochDay()
}
feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt (1)

21-31: 일일 고유 μ›Œν¬ 이름 μ „λž΅ OK β€” νƒœκ·Έ/λͺ¨λ‹ˆν„°λ§ μΆ”κ°€ ꢌμž₯

μœ λ‹ˆν¬ 넀이밍+KEEP 정책은 μ˜λ„μ— λΆ€ν•©ν•©λ‹ˆλ‹€. 운영 λͺ¨λ‹ˆν„°λ§μ„ μœ„ν•΄ νƒœκ·Έλ₯Ό μΆ”κ°€ν•˜κ³ , μ‹€νŒ¨/μž¬μ‹œλ„ 좔적이 ν•„μš”ν•˜λ©΄ 둜그/뢄석 이벀트λ₯Ό Workerμ—μ„œ λ°œν–‰ν•˜μ„Έμš”.

 val req = OneTimeWorkRequestBuilder<PostFortuneWorker>()
     .setConstraints(constraints)
-    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS)
+    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS)
+    .addTag(POST_FORTUNE_TAG)
     .build()
// 파일 ν•˜λ‹¨ λ“±
private const val POST_FORTUNE_TAG = "post_fortune"

λ˜ν•œ, PostFortuneWorkerκ°€ @HiltWorker둜 μ„ μ–Έλ˜κ³  HiltWorkerFactoryκ°€ Application에 μ—°κ²°λ˜μ–΄ μžˆλŠ”μ§€ 확인해 μ£Όμ„Έμš”.

domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt (1)

11-12: 이름 λͺ…ν™•μ„±: Epoch β†’ EpochDay ꢌμž₯

updateNoticeLastShownDateEpochFlowλŠ” λ°€λ¦¬μ΄ˆ epoch둜 μ˜€ν•΄λ  수 μžˆμŠ΅λ‹ˆλ‹€. 일 λ‹¨μœ„λΌλ©΄ ...EpochDayFlow와 같이 λͺ…ν™•νžˆ ν•˜λŠ” 것을 κ³ λ €ν•΄ μ£Όμ„Έμš”(도메인 μ „λ°˜ λ™μ‹œ 정리 μ „μ œ).

feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt (4)

220-224: LottieAnimation의 widthκ°€ λ¬΄μ‹œλ©λ‹ˆλ‹€ (λ‚΄λΆ€μ—μ„œ fillMaxWidth κ³ μ •).

ν˜„μž¬ LottieAnimation 컴포저블이 λ‚΄λΆ€μ μœΌλ‘œ modifier.fillMaxWidth()λ₯Ό κ°•μ œν•˜λ―€λ‘œ, ν˜ΈμΆœλΆ€μ˜ .width(375.dp)λŠ” νš¨κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€. μ˜λ„λŒ€λ‘œ κ°€λ‘œ/μ„Έλ‘œ λΉ„μœ¨μ„ λ§žμΆ”λ €λ©΄ aspectRatio둜 μ œμ–΄ν•˜κ±°λ‚˜ widthλ₯Ό μ œκ±°ν•˜μ„Έμš”.

μ•„λž˜μ²˜λŸΌ μˆ˜μ • ꢌμž₯:

-            LottieAnimation(
-                modifier = Modifier
-                    .width(375.dp)
-                    .height(267.dp),
-                resId = core.designsystem.R.raw.fortune_loading,
-            )
+            LottieAnimation(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .aspectRatio(375f / 267f),
+                resId = core.designsystem.R.raw.fortune_loading,
+            )

μΆ”κ°€ import:

+import androidx.compose.foundation.layout.aspectRatio

174-181: μŠ¬λΌμ΄λ”© 인디케이터 count ν•˜λ“œμ½”λ”©(6) β†’ μ‹€μ œ νŽ˜μ΄μ§€ μˆ˜μ™€ 동기화 ν•„μš”.

rememberPagerState(pageCount = { state.fortunePages.size + 2 })와 뢈일치 μ‹œ μ§€ν‘œ 개수 였λ₯˜κ°€ λ‚©λ‹ˆλ‹€. 동일 계산식 λ˜λŠ” pagerState.pageCount둜 μΉ˜ν™˜ν•˜μ„Έμš”.

-                SlidingIndicator(
-                    currentIndex = pagerState.currentPage,
-                    count = 6,
+                SlidingIndicator(
+                    currentIndex = pagerState.currentPage,
+                    count = pagerState.pageCount, // λŒ€μ•ˆ: state.fortunePages.size + 2

PagerState.pageCount κ°€μš© μ—¬λΆ€ 확인 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€. λΆˆκ°€ν•˜λ©΄ 동일 λžŒλ‹€ 계산식을 μ‚¬μš©ν•˜μ„Έμš”.


83-91: 빈 이벀트 νƒ€μž…("") λ‘œκΉ… λ°©μ§€.

else -> ""둜 μ„€μ •λœ κ²½μš°μ—λ„ 둜그λ₯Ό μŒ“μŠ΅λ‹ˆλ‹€. 곡백인 경우 λ‘œκΉ…μ„ μŠ€ν‚΅ν•˜μ„Έμš”.

-        analyticsHelper.logEvent(
-            AnalyticsEvent(
-                type = eventType,
-                properties = mapOf(
-                    AnalyticsEvent.FortunePropertiesKeys.FORTUNE_PAGE_NUMBER to pagerState.currentPage + 1,
-                ),
-            ),
-        )
+        if (eventType.isNotBlank()) {
+            analyticsHelper.logEvent(
+                AnalyticsEvent(
+                    type = eventType,
+                    properties = mapOf(
+                        AnalyticsEvent.FortunePropertiesKeys.FORTUNE_PAGE_NUMBER to pagerState.currentPage + 1,
+                    ),
+                ),
+            )
+        }

191-199: λ¬΄ν•œ 루프 LaunchedEffect λ‹¨μˆœν™” κ°€λŠ₯.

while(true) + delay λŒ€μ‹  rememberInfiniteTransition λ“± μ• λ‹ˆλ©”μ΄μ…˜ API둜 ν† κΈ€ν•˜λ©΄ 가독성과 μ·¨μ†Œ 처리 λͺ¨λ‘ κ°œμ„ λ©λ‹ˆλ‹€. κΈ°λŠ₯은 λ™μΌν•˜λ―€λ‘œ μ„ νƒμ‚¬ν•­μž…λ‹ˆλ‹€.

val transition = rememberInfiniteTransition(label = "deliverToggle")
val phase by transition.animateFloat(
    initialValue = 0f, targetValue = 1f,
    animationSpec = infiniteRepeatable(animation = tween(2000), repeatMode = RepeatMode.Reverse),
    label = "phase"
)
val isDelivering = phase > 0.5f
data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt (1)

20-21: Epoch λ‹¨μœ„ λͺ…μ‹œ ν•„μš”(초 vs λ°€λ¦¬μ΄ˆ).

updateNoticeLastShownDateEpochFlow: Flow<Long?>의 λ‹¨μœ„κ°€ 뢈λͺ…ν™•ν•©λ‹ˆλ‹€. μΈν„°νŽ˜μ΄μŠ€/도메인 μ „λ°˜μ—μ„œ epochMillis λ“±μœΌλ‘œ 이름을 λͺ…μ‹œν•˜κ±°λ‚˜ KDoc에 λ‹¨μœ„λ₯Ό κ³ μ •ν•΄ μ£Όμ„Έμš”.

data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSourceImpl.kt (1)

14-15: Epoch λ‹¨μœ„ λͺ…μ‹œ ν•„μš”(초 vs λ°€λ¦¬μ΄ˆ).

updateNoticeLastShownDateEpochFlow의 λ‹¨μœ„λ₯Ό μΈν„°νŽ˜μ΄μŠ€μ™€ μΌκ΄€λ˜κ²Œ λ¬Έμ„œν™”/λͺ…λͺ…ν•΄ μ£Όμ„Έμš”. μƒμœ„ λ ˆμ΄μ–΄μ™€ 동일 μ œμ•ˆ 적용 ꢌμž₯.

feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (2)

53-72: 쀑볡 Success λ°©μ§€: 동일 μƒνƒœ 재방좜 μ‹œ 쀑볡 fetch κ°€λŠ₯.

collectλŠ” 동일 Success μž¬λ°©μΆœμ—λ„ fetchAndUpdateFortuneλ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. distinctUntilChanged/collectLatest둜 λ””λ°”μš΄μŠ€ν•˜μ„Έμš”.

-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
@@
-    private fun observeFortune() = intent {
-        fortuneRepository.fortuneCreateStatusFlow.collect { status ->
+    private fun observeFortune() = intent {
+        fortuneRepository.fortuneCreateStatusFlow
+            .distinctUntilChanged()
+            .collectLatest { status ->

99-103: λ„€νŠΈμ›Œν¬ μ‹€νŒ¨ μ‹œ UX λŒ€μ‘ ν•„μš”.

getFortune μ‹€νŒ¨ μ‹œ λ‘œλ”©λ§Œ ν•΄μ œλ˜κ³  화면이 μ •μ§€ μƒνƒœκ°€ 될 수 μžˆμŠ΅λ‹ˆλ‹€. ν™ˆ 이동 λ˜λŠ” μž¬μ‹œλ„/μ•Œλ¦Ό ν† μŠ€νŠΈ 쀑 ν•˜λ‚˜λ‘œ μ²˜λ¦¬ν•˜μ„Έμš”.

         }.onFailure { error ->
             Log.e("FortuneViewModel", "μš΄μ„Έ 데이터 μš”μ²­ μ‹€νŒ¨: ${error.message}")
             reduce { state.copy(isLoading = false) }
+            postSideEffect(FortuneContract.SideEffect.NavigateToHome)
         }
core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt (2)

49-53: Broadcast μ‹œκ°„ μ œν•œ λŒ€λΉ„: Flow first()에 νƒ€μž„μ•„μ›ƒ ꢌμž₯.

λΈŒλ‘œλ“œμΊμŠ€νŠΈ μ»¨ν…μŠ€νŠΈμ—μ„œλŠ” 지연이 κΈΈμ–΄μ§€λ©΄ OSκ°€ 쀑단할 수 μžˆμŠ΅λ‹ˆλ‹€. withTimeout으둜 μ•ˆμ „μž₯치 μΆ”κ°€λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
@@
-                            val (fortuneCreateStatus, hasUnseenFortune) = withContext(Dispatchers.IO) {
-                                val status = fortuneRepository.fortuneCreateStatusFlow.first()
-                                val unseen = fortuneRepository.hasUnseenFortuneFlow.first()
-                                status to unseen
-                            }
+                            val (fortuneCreateStatus, hasUnseenFortune) = withContext(Dispatchers.IO) {
+                                withTimeout(3_000) {
+                                    val status = fortuneRepository.fortuneCreateStatusFlow.first()
+                                    val unseen = fortuneRepository.hasUnseenFortuneFlow.first()
+                                    status to unseen
+                                }
+                            }

81-82: Failure/Idle λΆ„κΈ° 처리 λͺ…μ‹œμ μž„.

아무 λ™μž‘ μ—†μŒμ΄ μ˜λ„λΌλ©΄ OKμž…λ‹ˆλ‹€. ν•„μš” μ‹œ λ‘œκΉ…/νŠΈλ ˆμ΄μ‹±λ§Œ μΆ”κ°€ κ²€ν† .

feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt (1)

49-55: μž¬μ‹œλ„ 기쀀을 ꡬ뢄(μž¬μ‹œλ„ κ°€λŠ₯/λΆˆκ°€)ν•˜μ„Έμš”.

μ„œλ²„ 4xx λ“± 영ꡬ μ‹€νŒ¨μ—μ„œλ„ Result.retry()λ₯Ό λ°˜ν™˜ν•˜λ©΄ λ°±μ˜€ν”„ μž¬μ‹œλ„κ°€ λΆˆν•„μš”ν•˜κ²Œ 계속될 수 μžˆμŠ΅λ‹ˆλ‹€. μ €μž₯μ†Œ/도메인 μ—λŸ¬ νƒ€μž…μ„ ꡬ뢄해 μž¬μ‹œλ„ λΆˆκ°€μΈ 경우 Result.failure()λ₯Ό λ°˜ν™˜ν•˜λŠ” λΆ„κΈ°λ₯Ό μΆ”κ°€ν•΄ μ£Όμ„Έμš”.

-                        onFailure = {
-                            fortuneRepository.markFortuneAsFailed()
-                            // WM λ°±μ˜€ν”„ κ·œμΉ™μ— 따라 μž¬μ‹œλ„
-                            Result.retry()
-                        },
+                        onFailure = { e ->
+                            fortuneRepository.markFortuneAsFailed()
+                            // 예: 도메인 μ˜ˆμ™Έ/HTTP μƒνƒœμ— 따라 μž¬μ‹œλ„ μ—¬λΆ€ κ²°μ •
+                            if (e is TransientNetworkException /* or 5xx */) {
+                                Result.retry()
+                            } else {
+                                Result.failure()
+                            }
+                        },
feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt (3)

110-115: μ•‘μ…˜ μ˜μ—­μ— μ ‘κ·Όμ„± Role λΆ€μ—¬.

λ²„νŠΌ 역할을 λͺ…μ‹œν•΄ μŠ€ν¬λ¦°λ¦¬λ” 탐색 ν’ˆμ§ˆμ„ λ†’μ—¬μ£Όμ„Έμš”.

-                Box(
+                Box(
                     modifier = Modifier
                         .weight(1f)
-                        .clickable(onClick = onDontShowAgain)
+                        .clickable(role = androidx.compose.ui.semantics.Role.Button, onClick = onDontShowAgain)
                         .padding(vertical = 14.dp),
                     contentAlignment = Alignment.Center,
                 ) {
@@
-                Box(
+                Box(
                     modifier = Modifier
                         .weight(1f)
-                        .clickable(onClick = onClose)
+                        .clickable(role = androidx.compose.ui.semantics.Role.Button, onClick = onClose)
                         .padding(vertical = 14.dp),
                     contentAlignment = Alignment.Center,
                 ) {

Also applies to: 124-128


64-70: 슀크림 클릭에 Ripple 제거 및 라벨 λΆ€μ—¬.

전체 슀크림 클릭에 λ¬Όκ²° νš¨κ³Όκ°€ λΆˆν•„μš”ν•˜λ©°, μŠ€ν¬λ¦°λ¦¬λ” 라벨을 λΆ€μ—¬ν•˜λ©΄ 더 λ‚«μŠ΅λ‹ˆλ‹€.

-    Box(
+    Box(
         modifier = Modifier
             .fillMaxSize()
-            .background(Color(0xFF17191F).copy(alpha = 0.85f))
-            .clickable(onClick = onClose),
+            .background(Color(0xFF17191F).copy(alpha = 0.85f))
+            .clickable(
+                onClickLabel = stringResource(id = R.string.update_notice_bottom_sheet_close),
+                indication = null,
+                interactionSource = remember { MutableInteractionSource() },
+                onClick = onClose,
+            ),
         contentAlignment = Alignment.BottomCenter,
     ) {

93-101: λ°°λ„ˆ 이미지 λ‘œλ”© crossfade μΆ”κ°€λ‘œ μ „ν™˜ ν’ˆμ§ˆ κ°œμ„ .

λ„€νŠΈμ›Œν¬ λ‘œλ”©μ‹œ κΉœλΉ‘μž„μ„ μ€„μž…λ‹ˆλ‹€.

+import coil.request.ImageRequest
@@
-                AsyncImage(
-                    model = imageUrl,
+                AsyncImage(
+                    model = ImageRequest.Builder(context)
+                        .data(imageUrl)
+                        .crossfade(true)
+                        .build(),
                     contentDescription = null,
                     contentScale = ContentScale.Crop,
                     modifier = Modifier
                         .fillMaxWidth()
                         .aspectRatio(1f),
                 )
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (1)

142-149: 두 Flowλ₯Ό μΌκ΄€λœ μŠ€λƒ…μƒ·μœΌλ‘œ 읽어 쑰건 νŒμ •ν•˜μ„Έμš”.

μ„œλ‘œ λ‹€λ₯Έ μ‹œμ μ˜ first() κ²°κ³Ό μ‘°ν•©μœΌλ‘œ 경계 νƒ€μ΄λ°μ—μ„œ 잘λͺ»λœ νŒλ‹¨μ΄ κ°€λŠ₯. combine으둜 μŠ€λƒ…μƒ·μ„ λ§Œλ“  λ’€ first()λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€.

-        val fortuneCreateStatus = fortuneRepository.fortuneCreateStatusFlow.first()
-        val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first()
+        val (fortuneCreateStatus, hasUnseenFortune) =
+            kotlinx.coroutines.flow.combine(
+                fortuneRepository.fortuneCreateStatusFlow,
+                fortuneRepository.hasUnseenFortuneFlow,
+            ) { a, b -> a to b }.first()
core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt (2)

112-115: Notification ID의 Int λ³€ν™˜ μ•ˆμ „ν™”.

Long β†’ Int μΊμŠ€νŒ…μ€ overflow μœ„ν—˜μ΄ μžˆμŠ΅λ‹ˆλ‹€. 항상 μ–‘μ˜ 32λΉ„νŠΈ λ²”μœ„λ‘œ λ§€ν•‘ν•˜μ„Έμš”.

-                startForeground(
-                    notificationId.toInt(),
-                    createNotification(alarm, shouldNavigateToMission(alarm.missionType)),
-                )
+                val notificationIdInt = (notificationId % Int.MAX_VALUE).toInt()
+                startForeground(
+                    notificationIdInt,
+                    createNotification(alarm, shouldNavigateToMission(alarm.missionType)),
+                )

199-206: μ„œλΉ„μŠ€ 생λͺ…μ£ΌκΈ° μŠ€μ½”ν”„λ‘œ 톡일.

별도 CoroutineScope λŒ€μ‹  serviceScopeλ₯Ό μ‚¬μš©ν•΄ μ·¨μ†ŒΒ·μ’…λ£Œ μ‹œ μΌκ΄€λ˜κ²Œ μ •λ¦¬λ˜λ„λ‘ ν•©λ‹ˆλ‹€.

-        CoroutineScope(Dispatchers.IO).launch {
+        serviceScope.launch {
             alarmUseCase.updateAlarmActive(
                 id = alarmId,
                 active = false,
             )
         }
feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (1)

457-464: λ„€νŠΈμ›Œν¬ νŒμ • μ™„ν™”/μ£Όμž…ν˜• Clock κ³ λ €.

  • VALIDATED λ―ΈμΆ©μ‘±(포털/초기 μ—°κ²°)μ—μ„œλ„ ν‘œμ‹œλ₯Ό 막을 수 μžˆμŠ΅λ‹ˆλ‹€. INTERNET만 만쑱 μ‹œ 일단 ν‘œμ‹œν•˜λ„λ‘ μ™„ν™”ν•˜κ±°λ‚˜, μ΅œμ†Œ μ§€μ—° ν›„ μž¬ν‰κ°€λ₯Ό κ³ λ €ν•˜μ„Έμš”.
  • LocalDate.now() 기반 둜직이 λ§ŽμœΌλ―€λ‘œ ν…ŒμŠ€νŠΈ μš©μ΄μ„±κ³Ό 경계 μ‹œμ (μžμ •) μ œμ–΄λ₯Ό μœ„ν•΄ Clock μ£Όμž…λ„ κ³ λ €ν•΄ λ³Ό λ§Œν•©λ‹ˆλ‹€.
data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt (2)

8-8: epoch λ‹¨μœ„ λͺ…ν™•νžˆ ν•΄μ£Όμ„Έμš”.

fortuneDateEpochFlowκ°€ epoch day(Long, toEpochDay) 기반이면 이름을 fortuneDateEpochDayFlow λ“±μœΌλ‘œ 더 κ΅¬μ²΄ν™”ν•˜κ±°λ‚˜ KDoc으둜 β€œepoch millisecondsκ°€ μ•„λ‹˜β€μ„ λͺ…μ‹œν•΄ μ£Όμ„Έμš”. ν˜Όλ™μœΌλ‘œ μΈν•œ 버그λ₯Ό 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.


12-12: Tooltip 넀이밍 일관성 깨짐(β€œToolTip” vs β€œTooltip”).

shouldShowFortuneToolTipFlow와 markFortuneTooltipShown()의 ν‘œκΈ°κ°€ λ‹€λ¦…λ‹ˆλ‹€. μΌκ΄€λ˜κ²Œ Tooltip둜 맞좰 μ£Όμ„Έμš”.

적용 예(μΈν„°νŽ˜μ΄μŠ€ κΈ°μ€€):

-    val shouldShowFortuneToolTipFlow: Flow<Boolean>
+    val shouldShowFortuneTooltipFlow: Flow<Boolean>

Also applies to: 21-21

feature/home/src/main/java/com/yapp/home/HomeScreen.kt (4)

416-422: API 레벨 λΆ„κΈ° λŒ€μ‹  μ•ˆμ „ μ˜μ—­(WindowInsets.safeDrawing) μ‚¬μš© ꢌμž₯.

SDK 35 λΆ„κΈ°λŠ” μœ μ§€λ³΄μˆ˜ λΉ„μš©μ΄ 크고 기기별/μ œμ‘°μ‚¬λ³„ 인셋 차이λ₯Ό ν‘μˆ˜ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€. μ•ˆμ „ν•˜κ²Œ WindowInsets.safeDrawing 기반으둜 상/ν•˜λ‹¨ 인셋을 ν•©μ‚°ν•˜μ„Έμš”.

-                                val offset = if (Build.VERSION.SDK_INT < 35) {
-                                    0.dp
-                                } else {
-                                    statusBarHeight + navBarHeight
-                                }
-                                sheetHalfExpandHeight = screenHeight - contentHeight - offset
+                                val safeInsets = WindowInsets.safeDrawing.asPaddingValues()
+                                val systemBarsOffset = safeInsets.calculateTopPadding() + safeInsets.calculateBottomPadding()
+                                sheetHalfExpandHeight = screenHeight - contentHeight - systemBarsOffset

λ‹€μ–‘ν•œ API(30/33/35) 및 제슀처 λ‚΄λΉ„κ²Œμ΄μ…˜/3‑button λ‚΄λΉ„κ²Œμ΄μ…˜μ—μ„œ 절반 ν™•μž₯ μœ„μΉ˜κ°€ μ˜λ„λŒ€λ‘œμΈμ§€ 검증 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.


515-518: μ•„μ΄μ½˜ μ ‘κ·Όμ„± 라벨 ν˜„μ§€ν™” ν•„μš”.

contentDescription = "Mail" / "Setting"은 ν•˜λ“œμ½”λ”©/μ˜λ¬Έμž…λ‹ˆλ‹€. stringResource둜 ν˜„μ§€ν™”λœ 라벨을 μ œκ³΅ν•΄ μ£Όμ„Έμš”.

예:

contentDescription = stringResource(id = R.string.cd_mail)
contentDescription = stringResource(id = R.string.cd_setting)

λ¬Έμžμ—΄ λ¦¬μ†ŒμŠ€κ°€ μ—†λ‹€λ©΄ μΆ”κ°€ ν•„μš”.

Also applies to: 556-560


604-607: μž₯μ‹μš© μ΄λ―Έμ§€λŠ” contentDescription을 null둜.

SkyImageλŠ” μž₯μ‹μš©μœΌλ‘œ λ³΄μž…λ‹ˆλ‹€. μŠ€ν¬λ¦°λ¦¬λ” 쀑볡 낭독을 λ°©μ§€ν•˜λ €λ©΄ contentDescription = null μ²˜λ¦¬ν•˜μ„Έμš”.

-        contentDescription = "IMG_MAIN_SKY",
+        contentDescription = null,

659-663: CD 일관화: μž₯μ‹μš©μ€ null, 의미 μžˆλŠ” μ»¨νŠΈλ‘€μ€ λ¦¬μ†ŒμŠ€λ‘œ.

  • 말풍선/별/빈 μƒνƒœ 일러슀트 λ“± μž₯μ‹μš©μ€ contentDescription = null ꢌμž₯.
  • AddAlarmButton λ‚΄ μ•„μ΄μ½˜μ€ μ˜† ν…μŠ€νŠΈκ°€ 라벨 역할을 ν•˜λ―€λ‘œ μ•„μ΄μ½˜ CDλŠ” null이 μ•ˆμ „ν•©λ‹ˆλ‹€(쀑볡 낭독 λ°©μ§€).

예:

-                contentDescription = "IMG_MAIN_SPEECH_BUBBLE",
+                contentDescription = null,
-            contentDescription = "Add Alarm",
+            contentDescription = null,

Also applies to: 672-680, 748-758, 827-831

data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt (2)

22-35: 였늘 νŒμ • 둜직의 μ‹œκ°„ μ†ŒμŠ€ 단일화/ν…ŒμŠ€νŠΈ μš©μ΄μ„± κ°œμ„ .

LocalDate.now() 기반 todayEpoch()λŠ” ν…ŒμŠ€νŠΈ/νƒ€μž„μ‘΄ 경계(μžμ • μ „ν›„)μ—μ„œ μ·¨μ•½ν•©λ‹ˆλ‹€. 곡용 DateProvider/Clockλ₯Ό μ£Όμž…ν•˜κ±°λ‚˜ UserPreferences의 동일 λ‘œμ§μ„ 단일 μ†ŒμŠ€λ‘œ λ…ΈμΆœν•΄(예: todayEpochDay()), μ—¬κΈ°μ„œλ„ κ·Έ 값을 μ‚¬μš©ν•˜μ„Έμš”. μ΄λ ‡κ²Œ ν•˜λ©΄ 도메인/λ°μ΄ν„°μŠ€ν† μ–΄ κ°„ 뢈일치 κ°€λŠ₯성을 μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


36-36: 쀑볡 μœ ν‹Έ 제거 μ œμ•ˆ.

todayEpoch()λŠ” UserPreferencesμ™€μ˜ 쀑볡 κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€. ν•œ 곳으둜 λͺ¨μœΌκ³  μž¬μ‚¬μš©ν•΄ μ£Όμ„Έμš”.

πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8852463 and f1fd6b6.

β›” Files ignored due to path filters (8)
  • core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable/ic_100_buble.png is excluded by !**/*.png
  • project.dot.png is excluded by !**/*.png
πŸ“’ Files selected for processing (70)
  • .github/workflows/android_ci.yml (0 hunks)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/AndroidManifest.xml (3 hunks)
  • app/src/main/java/com/yapp/orbit/OrbitApplication.kt (1 hunks)
  • app/src/main/java/com/yapp/orbit/OrbitNavHost.kt (4 hunks)
  • app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt (1 hunks)
  • build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt (1 hunks)
  • build-logic/src/main/java/orbit.android.feature.gradle.kts (0 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/AndroidAlarmScheduler.kt (5 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt (2 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt (3 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt (2 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt (1 hunks)
  • core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt (5 hunks)
  • core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt (1 hunks)
  • core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (3 hunks)
  • core/designsystem/src/main/res/raw/fortune_loading.json (1 hunks)
  • core/network/src/main/java/com/yapp/network/di/NetworkModule.kt (2 hunks)
  • core/network/src/main/java/com/yapp/network/di/Qualifier.kt (0 hunks)
  • core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt (1 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt (0 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt (0 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt (1 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt (2 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSource.kt (1 hunks)
  • data/src/main/java/com/yapp/data/local/datasource/UserLocalDataSourceImpl.kt (2 hunks)
  • data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt (1 hunks)
  • data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt (0 hunks)
  • data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (2 hunks)
  • data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt (1 hunks)
  • domain/src/main/java/com/yapp/domain/model/AlarmDay.kt (2 hunks)
  • domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt (1 hunks)
  • domain/src/main/java/com/yapp/domain/model/MissionMode.kt (1 hunks)
  • domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt (0 hunks)
  • domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt (1 hunks)
  • domain/src/main/java/com/yapp/domain/repository/UserInfoRepository.kt (1 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt (2 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionContract.kt (1 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (1 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt (3 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (1 hunks)
  • feature/fortune/build.gradle.kts (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt (2 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt (3 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (4 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt (1 hunks)
  • feature/home/build.gradle.kts (1 hunks)
  • feature/home/src/main/AndroidManifest.xml (1 hunks)
  • feature/home/src/main/java/com/yapp/home/HomeContract.kt (2 hunks)
  • feature/home/src/main/java/com/yapp/home/HomeScreen.kt (17 hunks)
  • feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (9 hunks)
  • feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt (2 hunks)
  • feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt (10 hunks)
  • feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (3 hunks)
  • feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt (1 hunks)
  • feature/home/src/main/res/values/strings.xml (2 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionContract.kt (0 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (2 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt (0 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt (0 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt (0 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt (0 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt (3 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt (1 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
πŸ’€ Files with no reviewable changes (12)
  • feature/mission/src/main/java/com/yapp/mission/MissionContract.kt
  • domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingBirthdayScreen.kt
  • .github/workflows/android_ci.yml
  • data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt
  • build-logic/src/main/java/orbit.android.feature.gradle.kts
  • data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingCompleteScreen2.kt
  • feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingAccessScreen.kt
  • data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt
  • core/network/src/main/java/com/yapp/network/di/Qualifier.kt
🧰 Additional context used
🧠 Learnings (4)
πŸ“š Learning: 2025-07-27T15:20:35.256Z
Learnt from: DongChyeon
PR: YAPP-Github/Orbit-Android#238
File: feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt:118-120
Timestamp: 2025-07-27T15:20:35.256Z
Learning: MissionRoute와 MissionScreen이 같은 파일(feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt)에 μžˆμ„ λ•Œ, MissionRouteμ—μ„œ BackHandlerλ₯Ό μ‚¬μš©ν•˜λ©΄ 파일 레벨의 import 문은 μœ μ§€λ˜μ–΄μ•Ό 함.

Applied to files:

  • feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt
  • core/common/src/main/java/com/yapp/common/navigation/route/MissionRoute.kt
πŸ“š Learning: 2025-09-14T15:32:32.628Z
Learnt from: DongChyeon
PR: YAPP-Github/Orbit-Android#252
File: feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt:24-56
Timestamp: 2025-09-14T15:32:32.628Z
Learning: μš΄μ„Έ μš”μ²­ κ°„μ—λŠ” μ΅œμ†Œ 1λΆ„ 간격이 보μž₯λ˜μ–΄ μžˆμ–΄μ„œ PostFortuneWorkerμ—μ„œ λ™μ‹œμ„±/μ›μžμ„± 문제λ₯Ό κ³ λ €ν•˜μ§€ μ•Šμ•„λ„ 됨

Applied to files:

  • feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt
πŸ“š Learning: 2025-07-23T10:29:14.146Z
Learnt from: DongChyeon
PR: YAPP-Github/Orbit-Android#234
File: feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt:73-76
Timestamp: 2025-07-23T10:29:14.146Z
Learning: AlarmMissionBottomSheetμ—μ„œ missionType/missionCount νŒŒλΌλ―Έν„°λŠ” ν˜„μž¬ μ €μž₯된 값을 UI에 ν‘œμ‹œν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜κ³ , selectedMissionType/selectedMissionCountλŠ” μ‚¬μš©μžκ°€ λ³€κ²½ 쀑인 λ‚΄λΆ€ μž‘μ—… μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€. onDoneμ΄λ‚˜ onSave μ½œλ°±μ„ 톡해 λͺ…μ‹œμ μœΌλ‘œ μ €μž₯ν•  λ•Œλ§Œ 변경사항이 λ°˜μ˜λ˜λŠ” UX νŒ¨ν„΄μ΄λ‹€.

Applied to files:

  • feature/home/src/main/res/values/strings.xml
  • feature/home/src/main/java/com/yapp/home/alarm/component/bottomsheet/AlarmMissionBottomSheet.kt
πŸ“š Learning: 2025-09-15T07:43:50.275Z
Learnt from: DongChyeon
PR: YAPP-Github/Orbit-Android#254
File: data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt:22-34
Timestamp: 2025-09-15T07:43:50.275Z
Learning: FortuneCreateStatusFlowμ—μ„œ todayEpoch()λ₯Ό combine λ‚΄λΆ€μ—μ„œ 직접 ν˜ΈμΆœν•˜λŠ” 것이 μΆ©λΆ„ν•œ 이유: μš΄μ„Έ 생성할 λ•Œλ§ˆλ‹€ fortuneDateEpochFlowκ°€ λ³€κ²½λ˜μ–΄ combine이 μž¬ν‰κ°€λ˜λ―€λ‘œ, κ·Έ μˆœκ°„μ˜ todayEpoch() κ³„μ‚°μœΌλ‘œ 좩뢄함. μš΄μ„Έ μš”μ²­ 간격 μ œμ•½μœΌλ‘œ 인해 μžμ • λ‘€μ˜€λ²„ λ¬Έμ œλŠ” μ‹€μš©μ μœΌλ‘œ λ°œμƒν•˜μ§€ μ•ŠμŒ.

Applied to files:

  • feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
🧬 Code graph analysis (10)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionActivity.kt (2)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt (1)
  • alarmInteractionNavGraph (33-69)
core/ui/src/main/java/com/yapp/ui/component/navigation/NavigationBarScrim.kt (1)
  • NavigationBarScrim (16-26)
feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt (1)
feature/fortune/src/main/java/com/yapp/fortune/page/FortuneCompletePage.kt (1)
  • FortuneCompletePage (28-104)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt (1)
core/designsystem/src/main/java/com/yapp/designsystem/theme/Theme.kt (1)
  • OrbitTheme (12-28)
feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt (2)
core/ui/src/main/java/com/yapp/ui/component/lottie/LottieAnimation.kt (1)
  • LottieAnimation (22-85)
core/designsystem/src/main/java/com/yapp/designsystem/theme/Theme.kt (1)
  • OrbitTheme (12-28)
feature/home/src/main/java/com/yapp/home/HomeScreen.kt (2)
feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (1)
  • processAction (55-87)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/UpdateNoticeBottomSheet.kt (1)
  • UpdateNoticeBottomSheet (51-139)
feature/home/src/main/java/com/yapp/home/alarm/addedit/AlarmAddEditScreen.kt (1)
core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetLayout.kt (1)
  • OrbitBottomSheetLayout (31-59)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (2)
feature/home/src/main/java/com/yapp/home/component/AlarmListDropDownMenu.kt (1)
  • AlarmListDropDownMenu (43-72)
feature/home/src/main/java/com/yapp/home/component/AlarmSortDropDownMenu.kt (1)
  • AlarmSortDropDownMenu (36-67)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingGenderScreen.kt (4)
core/ui/src/main/java/com/yapp/ui/component/bottomsheet/OrbitBottomSheetLayout.kt (1)
  • OrbitBottomSheetLayout (31-59)
feature/onboarding/src/main/java/com/yapp/onboarding/OnBoardingScreen.kt (1)
  • OnboardingScreen (14-54)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (1)
  • processAction (49-67)
core/ui/src/main/java/com/yapp/ui/toggle/OrbitGenderToggle.kt (1)
  • OrbitGenderToggle (30-98)
core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (1)
data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt (1)
  • todayEpoch (36-36)
data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt (1)
core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (1)
  • todayEpoch (44-44)
πŸͺ› detekt (1.23.8)
app/src/main/java/com/yapp/orbit/OrbitApplication.kt

[warning] 11-11: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (2)

102-114: snapshotFlow 쀑볡 이벀트 λ°©μ§€ κ°œμ„  ν•„μš”

ν˜„μž¬ κ΅¬ν˜„μ—μ„œλŠ” distinctUntilChanged()λ₯Ό μ μš©ν–ˆμ§€λ§Œ, LaunchedEffect의 keyκ°€ μ—¬μ „νžˆ Unitμž…λ‹ˆλ‹€. sheetStateλ₯Ό key둜 μ‚¬μš©ν•˜λŠ” 것이 더 μ•ˆμ „ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜μ„Έμš”:

-    LaunchedEffect(Unit) {
+    LaunchedEffect(sheetState) {
         snapshotFlow { sheetState.currentValue }
             .distinctUntilChanged()
             .collectLatest { value ->

98-98: Hidden μƒνƒœ 전이 λ°©μ§€ ν•„μš”

confirmValueChange 제거둜 μ‚¬μš©μžκ°€ λ“œλž˜κ·Έν•˜μ—¬ Hidden μƒνƒœλ‘œ 전이할 수 μžˆμŠ΅λ‹ˆλ‹€. μ‹œνŠΈκ°€ 항상 ν‘œμ‹œλ˜μ–΄μ•Ό ν•œλ‹€λ©΄ Hidden μƒνƒœλ₯Ό λͺ…μ‹œμ μœΌλ‘œ 차단해야 ν•©λ‹ˆλ‹€.

λ‹€μŒ 쀑 ν•˜λ‚˜λ₯Ό μ μš©ν•˜μ„Έμš”:

μ˜΅μ…˜ 1: Hidden μƒνƒœ κ±΄λ„ˆλ›°κΈ° (ꢌμž₯)

-    val sheetState = rememberStandardBottomSheetState()
+    val sheetState = rememberStandardBottomSheetState(
+        skipHiddenState = true
+    )

μ˜΅μ…˜ 2: confirmValueChange둜 λͺ…μ‹œμ  차단

-    val sheetState = rememberStandardBottomSheetState()
+    val sheetState = rememberStandardBottomSheetState(
+        confirmValueChange = { it != SheetValue.Hidden }
+    )
core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt (1)

30-31: goAsync 적용 κ΅Ώ + μ•± μ—…λ°μ΄νŠΈ ν›„ μž¬μŠ€μΌ€μ€„λ„ ν•¨κ»˜ 처리 μ œμ•ˆ

λΆ€νŒ… ν›„λ§Œ μ²˜λ¦¬ν•˜λ©΄ μ•± μ—…λ°μ΄νŠΈ 직후 μ•ŒλžŒ μž¬μ„€μ •μ΄ λˆ„λ½λ  수 μžˆμŠ΅λ‹ˆλ‹€. ACTION_MY_PACKAGE_REPLACED도 ν•¨κ»˜ μˆ˜μ‹ ν•΄ 동일 λ‘œμ§μ„ νƒœμš°λŠ” κ±Έ ꢌμž₯ν•©λ‹ˆλ‹€. (이전 리뷰 μ½”λ©˜νŠΈμ™€ 동일 μ œμ•ˆ)

-        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
+        if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) {
             val pending = goAsync()
             rescheduleAlarm(pending)
         }
🧹 Nitpick comments (1)
core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt (1)

35-44: μ˜ˆμ™Έ λ‘œκΉ… μΆ”κ°€λ‘œ 문제 진단성 κ°œμ„  μ œμ•ˆ

ν˜„μž¬ μ˜ˆμ™Έκ°€ μ‚ΌμΌœμ Έ 원인 νŒŒμ•…μ΄ μ–΄λ ΅μŠ΅λ‹ˆλ‹€. catchμ—μ„œ λ‘œκΉ… ν›„ finish 보μž₯ μœ μ§€ ꢌμž₯.

-        CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
-            try {
-                val alarms = alarmUseCase.getAllAlarms().first()
-                alarms
-                    .filter { it.isAlarmActive }
-                    .forEach { alarm -> androidAlarmScheduler.scheduleAlarm(alarm) }
-            } finally {
-                pendingResult.finish()
-            }
-        }
+        CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
+            try {
+                val alarms = alarmUseCase.getAllAlarms().first()
+                alarms
+                    .filter { it.isAlarmActive }
+                    .forEach { alarm -> androidAlarmScheduler.scheduleAlarm(alarm) }
+            } catch (t: Throwable) {
+                android.util.Log.e("RescheduleAlarmReceiver", "Failed to reschedule alarms", t)
+            } finally {
+                pendingResult.finish()
+            }
+        }
πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f1fd6b6 and 700e803.

πŸ“’ Files selected for processing (2)
  • core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt (2 hunks)
  • feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (2)
feature/home/src/main/java/com/yapp/home/component/AlarmListDropDownMenu.kt (1)
  • AlarmListDropDownMenu (43-72)
feature/home/src/main/java/com/yapp/home/component/AlarmSortDropDownMenu.kt (1)
  • AlarmSortDropDownMenu (36-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: Continuous Deployment
πŸ”‡ Additional comments (4)
feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt (2)

329-341: λ“œλ‘­λ‹€μš΄ 메뉴 쑰건뢀 λ Œλ”λ§ 제거 확인

AlarmListDropDownMenu와 AlarmSortDropDownMenuκ°€ 이제 항상 λ Œλ”λ§λ˜λ©°, expanded μƒνƒœλ§Œ μ „λ‹¬λ©λ‹ˆλ‹€. μ΄λŠ” μ„±λŠ₯상 λ¬Έμ œκ°€ μ—†μ§€λ§Œ, 두 메뉴가 λ™μ‹œμ— 열릴 κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€.

두 메뉴가 λ™μ‹œμ— 열리지 μ•Šλ„λ‘ μƒνƒœ 관리가 적절히 되고 μžˆλŠ”μ§€ 확인이 ν•„μš”ν•©λ‹ˆλ‹€. 특히 menuExpanded와 sortDropDownMenuExpandedκ°€ μƒν˜Έ λ°°νƒ€μ μœΌλ‘œ κ΄€λ¦¬λ˜λŠ”μ§€ κ²€μ¦ν•˜μ„Έμš”.


93-93: onExpanded 콜백 μ‚¬μš© 확인 β€” 해결됨

feature/home/src/main/java/com/yapp/home/HomeScreen.kt (lines 394–396)μ—μ„œ onExpandedκ°€ ν˜ΈμΆœλ˜μ–΄ processAction(HomeContract.Action.HideToolTip)λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. μˆ˜μ • λΆˆν•„μš”.

core/alarm/src/main/java/com/yapp/alarm/receivers/RescheduleAlarmReceiver.kt (2)

11-12: ν•„μš” import μΆ”κ°€ 적절

SupervisorJob, flow.first λ„μž… λͺ¨λ‘ 상황에 맞고 πŸ‘


29-33: Manifest μ„€μ • 확인: RECEIVE_BOOT_COMPLETEDΒ·intent-filterΒ·exported 확인됨

app/src/main/AndroidManifest.xml에 RECEIVE_BOOT_COMPLETED κΆŒν•œ(라인 11)κ³Ό RescheduleAlarmReceiver λ¦¬μ‹œλ²„ μ„ μ–Έ(라인 72–76) β€” intent-filter(android.intent.action.BOOT_COMPLETED) 및 android:exported="true"(라인 73)κ°€ μ‘΄μž¬ν•˜λ―€λ‘œ BOOT_COMPLETED μˆ˜μ‹  섀정은 μΆ©μ‘±λ©λ‹ˆλ‹€.

@DongChyeon DongChyeon merged commit 94aeb45 into develop Sep 16, 2025
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

β€οΈβ€πŸ©Ή CHORE μž‘μ€ μˆ˜μ •(νƒ€μž…λ³€μˆ˜, νŒ¨ν‚€μ§€κ΅¬μ‘° λ³€κ²½ λ“±)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants