Skip to content

Conversation

@DongChyeon
Copy link
Member

@DongChyeon DongChyeon commented Jul 29, 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

  • 신규 기능

    • 미션 모드(REAL/PREVIEW) 추가, 포춘 딥링크 및 로딩 애니메이션 적용, WorkManager 기반 데일리 포춘 스케줄러 도입.
  • 버그 수정

    • 일부 오류 대화상자 및 불필요한 네비게이션 경로 정리로 오류 흐름 개선.
  • 리팩터링

    • 포춘 상태·저장소 및 네트워크/DI 구조 정비, 레이아웃 계층·시스템 바 패딩 간소화.
  • UI/스타일

    • 미션 선택/바텀시트 UX 개선 및 내비게이션 바 처리 개선, 업데이트 공지 바텀시트 추가.
  • 기타

    • 앱 버전 1.1.3(6) 업데이트, CI 트리거 변경 및 앱 시작 시 작업 초기화 추가.

DongChyeon and others added 30 commits June 10, 2025 18:47
…toryimpl 디렉토리로 통합하여 도메인 중심 구조로 정리
…ncy-cleanup

[REFACTOR] 모듈간의 의존성을 깔끔하게 정리해보자
- 모든 유닛 테스트에 대해 Jacoco 설정 자동 적용
- Android 모듈(Application, Library)에 유닛 테스트 커버리지 활성화
- 커버리지 리포트를 생성하는 generateTestCoverageReport Task 등록
- .exec 파일 미존재 시 createDebugUnitTestCoverageReport Task 자동 스킵
[FEAT] Junit4 테스트 환경을 설정해보자.
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: 3

🧹 Nitpick comments (17)
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2)

133-136: 마지막 스텝에서 햅틱이 이중으로 재생됩니다

handleMissionProgress(마지막 단계)에서 이미 성공 햅틱을 발생시키고, completeMission에서도 다시 호출합니다. 중복 진동을 제거하세요.

적용 diff:

 private fun completeMission(type: String) = intent {
-        performHapticSuccess()
         logMissionSuccess(type)

137-140: 변수명 오타(fortuneCreateStaus) → fortuneCreateStatus

가독성을 위해 정정 권장합니다.

-            val fortuneCreateStaus = fortuneRepository.fortuneCreateStatusFlow.first()
+            val fortuneCreateStatus = fortuneRepository.fortuneCreateStatusFlow.first()
 
-            when (fortuneCreateStaus) {
+            when (fortuneCreateStatus) {
core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt (3)

34-44: 미션 데이터 유효성: missionCount=0 처리 여부 확인

현재 -1만 배제합니다. 0 회차가 유효하지 않다면 조건을 > 0으로 강화하세요.

-                        missionCount != -1
+                        missionCount > 0

55-82: 중복 Fortune 인텐트 생성 제거 권장

두 분기(Creating, Success+hasUnseenFortune)에서 동일 인텐트를 중복 생성합니다. 헬퍼로 추출해 중복 제거하세요.

적용 diff(해당 범위 치환):

-                                is FortuneCreateStatus.Creating -> {
-                                    context?.let { ctx ->
-                                        val uri = "orbitapp://fortune".toUri()
-                                        val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply {
-                                            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                                            setPackage(ctx.packageName)
-                                        }
-                                        ctx.startActivity(fortuneIntent)
-                                    }
-                                }
+                                is FortuneCreateStatus.Creating -> {
+                                    context?.launchFortune()
+                                }
@@
-                                is FortuneCreateStatus.Success -> {
-                                    if (hasUnseenFortune) {
-                                        context?.let { ctx ->
-                                            val uri = "orbitapp://fortune".toUri()
-                                            val fortuneIntent =
-                                                Intent(Intent.ACTION_VIEW, uri).apply {
-                                                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                                                    setPackage(ctx.packageName)
-                                                }
-                                            ctx.startActivity(fortuneIntent)
-                                        }
-                                    }
-                                }
+                                is FortuneCreateStatus.Success -> {
+                                    if (hasUnseenFortune) context?.launchFortune()
+                                }

클래스 내부에 다음 헬퍼 추가:

private fun Context.launchFortune() {
    val uri = "orbitapp://fortune".toUri()
    val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply {
        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        setPackage(packageName)
    }
    startActivity(fortuneIntent)
}

84-93: URI 문자열 수동 조립 대신 Uri.Builder 사용 추천

가독성과 안전성(인코딩)을 위해 Builder/Uri 대신 문자열 연결을 지양하세요.

-                                val uriString =
-                                    "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount"
-                                val missionIntent =
-                                    Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
+                                val uri = android.net.Uri.Builder()
+                                    .scheme("orbitapp")
+                                    .authority("mission")
+                                    .appendQueryParameter("notificationId", notificationId.toString())
+                                    .appendQueryParameter("missionType", missionType.value.toString())
+                                    .appendQueryParameter("missionCount", missionCount.toString())
+                                    .build()
+                                val missionIntent = Intent(Intent.ACTION_VIEW, uri).apply {
                                         addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                                         setPackage(ctx.packageName)
                                     }
domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt (1)

3-8: 상태 모델링 적절

생성 파이프라인을 명확히 캡슐화합니다. 이후 필요 시 Failure에 원인(payload) 확장을 고려해도 좋습니다.

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

33-38: userId 부재 시 실패 반환 정책 확인 필요

현재 Result.failure()로 종료되어 재시도되지 않습니다. 로그인 지연/복귀 시 재시도를 원한다면 Result.retry() 또는 별도 재스케줄 전략이 필요합니다. 제품 의도 확인 부탁드립니다.


41-59: 상태 전이 및 재시도 처리 양호

Creating → (성공) Created/점수저장, (실패/예외) Failed 후 retry 흐름이 일관적입니다.

스케줄러 측에서는 UniqueWork + KEEP 사용으로 중복 작업을 억제하는 것을 권장합니다.

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

53-72: 수집 방식 개선 제안: collectLatest + 중복 억제

빠른 상태 전이에서 이전 처리 취소와 과도한 재호출 방지를 위해 collectLatest와 distinctUntilChanged를 고려하세요.

-    private fun observeFortune() = intent {
-        fortuneRepository.fortuneCreateStatusFlow.collect { status ->
+    private fun observeFortune() = intent {
+        fortuneRepository.fortuneCreateStatusFlow
+            .distinctUntilChanged()
+            .collectLatest { status ->
             when (status) {

필요 import:

import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged

61-65: first() 호출의 스레드 고려

isFirstAlarmDismissedTodayFlow가 디스크 I/O(DataStore 등)를 동반한다면, withContext(Dispatchers.IO)로 감싸는 것을 권장합니다. 내부가 이미 IO로 전환된다면 현행 유지해도 무방합니다.

data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (3)

22-22: 네이밍 일관성: Tooltip vs ToolTip 표기 혼재

shouldShowFortuneToolTipFlowToolTip(대문자 T, P)로 쓰이고, 다른 곳(예: markFortuneTooltipShown)은 Tooltip으로 사용됩니다. 한 가지 표기로 통일해 주세요.

적용 예:

-    override val shouldShowFortuneToolTipFlow: Flow<Boolean> = fortuneLocalDataSource.shouldShowFortuneToolTipFlow
+    override val shouldShowFortuneTooltipFlow: Flow<Boolean> = fortuneLocalDataSource.shouldShowFortuneTooltipFlow

27-31: API 네이밍 정렬 제안: markFortuneAs vs markFortune**

레포지토리 계층은 markFortuneAsCreating/Created/Failed, 로컬 DS는 markFortuneCreating/Created/Failed로 접두사만 달라 가독성이 떨어집니다. 도메인/데이터 계층 전반에서 동일한 패턴으로 통일하는 것을 권장합니다.


40-41: toDomain 변환 예외 가능성 확인 및 map vs mapCatching 선택

mapCatching { it.toDomain() } 사용은 안전합니다. 만약 toDomain()이 예외를 던지지 않도록 보장된다면 map { ... }가 더 간결합니다. 그렇지 않다면 현재 구현 유지가 맞습니다. 확인 부탁드립니다.

Also applies to: 45-46

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

12-12: 네이밍 일관성: Tooltip vs ToolTip

프로퍼티 shouldShowFortuneToolTipFlow의 표기를 Tooltip으로 통일해 주세요(메서드 markFortuneTooltipShown과 일치).

적용 예:

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

13-13: “오늘” 기준의 시간대/경계 정의를 인터페이스에서 명시해 주세요

isFirstAlarmDismissedTodayFlow/markFirstAlarmDismissedToday()의 “Today” 기준(로컬 타임존? 서버 타임? 자정 기준?)을 KDoc으로 명시하는 것이 안전합니다. 타임존 변경/서머타임 전환 시 오해 소지가 있습니다.

Also applies to: 24-24

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

23-36: 다중 수집자에서 중복 결합 비용 절감

combine(...).distinctUntilChanged()는 cold flow라 다중 수집 시 결합이 반복됩니다. 핫으로 승격해 캐시를 두면 비용과 지연을 줄일 수 있습니다. 예: stateIn(appScope, SharingStarted.Eagerly, FortuneCreateStatus.Idle).


20-20: 네이밍 일관성: Tooltip vs ToolTip

여기서는 shouldShowFortuneToolTipFlow(ToolTip)와 markFortuneTooltipShown(Tooltip)이 혼재합니다. Tooltip으로 통일 권장.

적용 예:

-    override val shouldShowFortuneToolTipFlow = userPreferences.shouldShowFortuneToolTipFlow
+    override val shouldShowFortuneTooltipFlow = userPreferences.shouldShowFortuneTooltipFlow

Also applies to: 55-57

📜 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 117f65d and 0c0c8a9.

📒 Files selected for processing (10)
  • core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt (2 hunks)
  • core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (2 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 (3 hunks)
  • data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (2 hunks)
  • domain/src/main/java/com/yapp/domain/model/FortuneCreateStatus.kt (1 hunks)
  • domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (4 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt (1 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt
  • domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-14T15:32:32.587Z
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.587Z
Learning: 운세 요청 간에는 최소 1분 간격이 보장되어 있어서 PostFortuneWorker에서 동시성/원자성 문제를 고려하지 않아도 됨

Applied to files:

  • feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt
⏰ 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 (9)
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (1)

10-10: 적절한 import 추가

새 모델/Flow 기반 전환에 필요한 의존성 추가가 타당합니다.

Also applies to: 17-17

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

45-48: goAsync + finally { pending.finish() } 패턴 적절

비동기 처리에서 브로드캐스트 수명 보장을 잘 처리했습니다.

Also applies to: 95-97


49-55: I/O 경계 분리 적절

Flow 읽기를 IO 컨텍스트로 옮긴 점 좋습니다.

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

23-29: 중복 요청 회피 로직 적절

Creating/Success 조기 반환으로 중복 포스팅을 방지합니다.

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

33-34: 초기 observeFortune 전환 좋습니다

상태 기반 반응형 흐름이 명확해졌습니다.


86-88: 포춘 열람 상태 선반영 좋습니다

markFortuneSeen()을 성공 처리 직후 호출하는 순서가 자연스럽습니다.

data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (1)

36-36: 일괄 초기화 진입점 추가는 적절합니다

clearFortuneData()로 상태를 한 번에 초기화하는 방향은 사용성/일관성 측면에서 좋습니다.

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

4-8: 확인: java.time 사용에 대한 하위 호환 문제 없음

프로젝트 minSdk가 28(≥26)이고 build-logic의 compileOptions가 JavaVersion.VERSION_17로 설정되어 있어 core library desugaring(디슈가링)은 필요하지 않습니다.

확인 위치: build-logic/src/main/java/com/yapp/convention/KotlinAndroid.kt (defaultConfig minSdk = 28, compileOptions = JavaVersion.VERSION_17), gradle/libs.versions.toml (minSdk = "28").

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

26-26: 원자성 확인 — DataStore.edit 단일 트랜잭션으로 처리됨
clearFortuneData는 core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt 199–202행에서 dataStore.edit { pref.remove(Keys.FORTUNE_ID); pref.remove(Keys.FORTUNE_DATE) } 형태로 구현되어 있어 동일 트랜잭션 내에서 키 제거가 수행되며 원자성이 보장됩니다.

@DongChyeon DongChyeon changed the base branch from develop to main September 15, 2025 07:51
@DongChyeon DongChyeon added ❤️‍🩹 CHORE 작은 수정(타입변수, 패키지구조 변경 등) and removed ✨ FEAT 기능 개발 labels Sep 15, 2025
@DongChyeon DongChyeon changed the title [CHORE] 1.1.3 버전 배포 [RELEASE] v1.1.3 Sep 15, 2025
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 (2)
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (1)

137-156: REAL 모드에서 스케줄 미보장 시 Idle→뒤로가기 문제 재발 가능

현재 상태 조회만 수행하고 있어, 작업이 아직 enqueue되지 않은 타이밍엔 Idle로 판단되어 뒤로가기가 발생할 수 있습니다. 미션 완료 시점에 스케줄을 보장한 뒤 상태를 판정하세요. 이전 리뷰와 동일 이슈입니다.

아래처럼 상태 읽기 전에 스케줄 호출을 추가하는 것을 권장합니다:

     private fun completeMission(type: String) = intent {
         performHapticSuccess()
         logMissionSuccess(type)

         if (state.missionMode != MissionMode.REAL) {
             postSideEffect(MissionContract.SideEffect.NavigateBack)
             return@intent
         }

+        // 1) 오늘 작업 스케줄 보장
+        postFortuneTaskScheduler.enqueueOnceForToday()
+
         val fortuneCreateStatus = fortuneRepository.fortuneCreateStatusFlow.first()
         val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first()

-        val shouldOpenFortune = (
-            fortuneCreateStatus is FortuneCreateStatus.Creating ||
-                fortuneCreateStatus is FortuneCreateStatus.Success && hasUnseenFortune
-            )
+        val shouldOpenFortune =
+            fortuneCreateStatus is FortuneCreateStatus.Creating ||
+            (fortuneCreateStatus is FortuneCreateStatus.Success && hasUnseenFortune) ||
+            // 2) Idle이라면 스케줄 직후 대기/진입 전략 중 택1
+            fortuneCreateStatus is FortuneCreateStatus.Idle

주입 추가(파일 외 변경; 예시):

// 생성자에 스케줄러 주입
class MissionViewModel @Inject constructor(
    private val analyticsHelper: AnalyticsHelper,
    private val hapticFeedbackManager: HapticFeedbackManager,
    private val fortuneRepository: FortuneRepository,
    private val app: Application,
    private val savedStateHandle: SavedStateHandle,
    private val postFortuneTaskScheduler: PostFortuneTaskScheduler, // 새로 주입
) : ViewModel(), ...
data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt (1)

22-34: 상태 우선순위(실패 > 생성중 > 성공 > 대기) 적절 — 자정 경계 재평가와 Clock 주입은 선택

현재 결합 로직은 단순·명확하고 distinctUntilChanged()로 불필요한 방출을 억제합니다. 다만 todayEpoch()는 업스트림 플로우 변화가 없으면 자정에 재평가되지 않습니다. 과거에 동일 제안을 드렸고(테스트·타임존 개선 목적의 Clock 주입 및 자정 tick 플로우), 팀에서 “운세 생성 시에만 상태 갱신이면 충분”하다고 정리된 것으로 알고 있습니다. 그 결론 유지에 동의하며, 아래는 선택 적용 가능한 최소 변경안입니다.

@@
-import java.time.LocalDate
+import java.time.Clock
+import java.time.LocalDate
@@
-class FortuneLocalDataSourceImpl @Inject constructor(
-    private val userPreferences: UserPreferences,
-) : FortuneLocalDataSource {
+class FortuneLocalDataSourceImpl @Inject constructor(
+    private val userPreferences: UserPreferences,
+    private val clock: Clock,
+) : FortuneLocalDataSource {
@@
-    private fun todayEpoch(): Long = LocalDate.now().toEpochDay()
+    private fun todayEpoch(): Long = LocalDate.now(clock).toEpochDay()

자정 자동 재평가가 필요해질 경우에만 todayEpochFlow를 추가해 combine(..., todayEpochFlow)로 확장하는 방식을 권장드립니다.

🧹 Nitpick comments (19)
feature/home/src/main/AndroidManifest.xml (1)

3-3: 권한 선언 위치를 앱 모듈로 일원화 검토

feature 모듈(라이브러리)에서 ACCESS_NETWORK_STATE를 선언하면 최종 병합에는 문제없지만, 권한 관리가 분산되어 추적/검토가 어려워집니다. 앱 모듈 app/src/main/AndroidManifest.xml에 이미 선언되어 있다면 여기서는 제거하는 편이 권장됩니다. WorkManager 네트워크 제약만 사용한다면 대개 권한 없이도 동작하지만, ConnectivityManager 직접 조회가 있다면 앱 모듈에서만 선언해 주세요.

제거 예:

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

해당 feature 내에서 ConnectivityManager/네트워크 콜백을 직접 사용 중인지 확인 부탁드립니다. 앱 모듈에도 동일 권한이 있는 경우 중복 선언을 정리해 주세요.

feature/home/build.gradle.kts (1)

22-22: Coil 네트워크 스택/버전 정합성 점검 제안

  • 원격 이미지를 로드한다면 coil-network-okhttp(또는 카탈로그의 libs.coil.network.okhttp) 추가를 고려하세요.
  • 모듈 간 Coil 아티팩트가 늘어날 가능성이 있다면 BOM(또는 버전 카탈로그 단일 버전)으로 정합성 확보를 권장합니다.
app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt (1)

14-17: @nAmed 대신 타입‑세이프한 @qualifier 사용 권장

문자열 키 오타를 방지하려면 전용 @Qualifier로 교체하세요.

다음처럼 같은 파일에 정의/적용 가능합니다:

 import dagger.hilt.components.SingletonComponent
-import javax.inject.Named
 import javax.inject.Singleton
+import javax.inject.Qualifier
+import kotlin.annotation.AnnotationRetention.BINARY
+
+@Qualifier
+@Retention(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
 }

주입 지점의 @Named("appVersion")@AppVersion로 변경 필요.

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

9-10: Epoch 단위 명시 필요 (day vs millis)

updateNoticeLastShownDateEpochFlowtoEpochDay()(일 단위)인지, epoch millis인지가 이름만으론 불명확합니다. 혼동 방지를 위해 KDoc 또는 명확한 네이밍(예: ...EpochDayFlow 또는 ...EpochMillisFlow)을 권장합니다.

 interface UserLocalDataSource {
@@
-    val updateNoticeLastShownDateEpochFlow: Flow<Long?>
+    /**
+     * 마지막 노출 일자(UTC/시스템 타임존 기준)의 epoch day.
+     * 값은 LocalDate.toEpochDay() 결과(Long) 또는 null.
+     */
+    val updateNoticeLastShownEpochDayFlow: Flow<Long?>

15-16: “오늘” 개념의 시간대 의존성 제거 권장

데이터 계층에서 “today”를 결정하면 타임존 변경/테스트가 어렵습니다. 호출 측에서 epochDay를 계산해 전달하도록 오버로드 추가를 권장합니다(기존 API는 유지 후 deprecated).

 interface UserLocalDataSource {
@@
-    suspend fun markUpdateNoticeShownToday()
+    /**
+     * 호출자가 계산한 epoch day 기준으로 노출 시점을 기록.
+     */
+    suspend fun markUpdateNoticeShown(epochDay: Long)
+    @Deprecated("시간대/테스트 독립성을 위해 markUpdateNoticeShown(epochDay)를 사용하세요.")
+    suspend fun markUpdateNoticeShownToday()
domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt (2)

9-9: Epoch 단위 문서화/네이밍 정교화

fortuneDateEpochFlow가 epoch day(Long)라면 이름을 fortuneEpochDayFlow 등으로 바꾸거나 KDoc으로 단위를 명시해 주세요.

 interface FortuneRepository {
@@
-    val fortuneDateEpochFlow: Flow<Long?>
+    /**
+     * 오늘 생성된 포춘의 날짜(epoch day, LocalDate.toEpochDay()) 또는 null.
+     */
+    val fortuneEpochDayFlow: Flow<Long?>

25-26: “오늘” 의존 메서드의 타임존 리스크

markFirstAlarmDismissedToday() 역시 시간대 경계(자정) 이슈가 있습니다. 호출자가 epochDay를 전달하는 오버로드 추가를 권장합니다(기존 API는 deprecated 유지).

-    suspend fun markFirstAlarmDismissedToday()
+    suspend fun markFirstAlarmDismissed(epochDay: Long)
+    @Deprecated("시간대/테스트 독립성을 위해 markFirstAlarmDismissed(epochDay)를 사용하세요.")
+    suspend fun markFirstAlarmDismissedToday()
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (1)

47-49: null 처리 의도 확인 및 Clock 주입 제안

  • fortuneDate == null이면 항상 isFirstMission = true가 됩니다. 의도라면 OK, 아니라면 null을 “모름=오늘 아님”이 아닌 “모름=오늘일 수도”로 다르게 처리해야 할 수 있습니다.
  • 테스트 용이성과 일관성을 위해 Clock(또는 ZoneId)을 주입받아 LocalDate.now(clock)로 계산하도록 리팩터를 권장합니다.
-        val todayDate = LocalDate.now().toEpochDay()
+        val todayDate = LocalDate.now().toEpochDay()
+        // 필요 시: val todayDate = LocalDate.now(clock).toEpochDay()
-        val isFirstMission = fortuneDate != todayDate
+        val isFirstMission = fortuneDate?.let { it != todayDate } ?: /* null 전략 */ true
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (1)

133-135: 해프틱 성공 중복 호출 가능성

handleMissionProgress()(라인 105 부근)와 completeMission()(라인 134)에서 SUCCESS 해프틱이 두 번 울릴 수 있습니다. 한 곳으로 일원화하세요.

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

81-86: 자정 롤오버 시 UI가 갱신되지 않을 수 있음 (DataStore flow는 변경 시에만 emit).

todayEpoch()는 map 내부에서 계산되지만, Preferences 자체 변경이 없으면(자정에 아무 edit도 없으면) 해당 flows가 재‑emit되지 않아 다음 날로 넘어가도 값이 갱신되지 않을 수 있습니다. hasUnseenFortuneFlow, shouldShowFortuneToolTipFlow, isFirstAlarmDismissedTodayFlow 모두 동일 리스크가 있습니다. 실제로는 다른 편집 이벤트가 자주 발생하면 문제가 드러나지 않을 수 있으나, 보수적으로 자정 롤오버 시 재평가 트리거를 두는 편이 안전합니다.

  • 옵션 A: Preferences와 무관하게 자정에 1회 emit하는 todayEpochFlow()를 만들어 combine 하세요.
  • 옵션 B: 앱 포그라운드 진입/재개 시 한 번 invalidate(예: viewModel에서 load* 호출)하여 새 epoch 기준으로 recompute 하세요.

필요 시 todayEpochFlow 구현 초안을 제안할 수 있습니다. 적용 의향 확인 부탁드립니다.

Also applies to: 89-97, 108-116


44-45: Clock 주입으로 테스트 용이성 및 재현성 향상.

로컬 타임존/시간 의존 로직의 테스트를 쉽게 하려면 Clock을 주입받아 todayEpoch을 계산하는 방식을 권장합니다(기존 호출부 영향 없음).

+import java.time.Clock
 ...
-    private fun todayEpoch(): Long = LocalDate.now().toEpochDay()
+    private fun todayEpoch(clock: Clock = Clock.systemDefaultZone()): Long =
+        LocalDate.now(clock).toEpochDay()
feature/home/src/main/java/com/yapp/home/HomeContract.kt (1)

21-22: 업데이트 공지 가시성 상태 추가 LGTM. 다만 용어 정합성 점검 권장.

ViewModel에서 shouldShowFortuneToolTipFlow를 hasNewFortune에 매핑하고 있어 의미가 살짝 어긋납니다. 차후 혼동 방지를 위해 hasNewFortune → shouldShowFortuneTooltip(또는 newFortuneBadgeVisible) 식으로 이름 정리 고려 부탁드립니다.

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

50-62: 버전/배너 URL 계산을 UI 밖으로 분리 권장.

테스트 용이성과 재사용성을 위해 versionName, imageUrl 계산은 VM/호출 측에서 주입하고, 컴포저블은 순수하게 그리기만 하도록 유지하는 것을 권장합니다.

feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (2)

366-373: 명칭 혼동: shouldShowTooltip을 hasNewFortune으로 전달.

유지보수성을 위해 지역 변수/상태명을 tooltip 의미로 통일하는 것이 좋습니다.

-        }.collect { (finalFortuneScore, hasNewFortune) ->
+        }.collect { (finalFortuneScore, shouldShowTooltip) ->
             reduce {
                 state.copy(
                     lastFortuneScore = finalFortuneScore,
-                    hasNewFortune = hasNewFortune,
-                    isToolTipVisible = hasNewFortune,
+                    hasNewFortune = shouldShowTooltip,
+                    isToolTipVisible = shouldShowTooltip,
                 )
             }
         }

457-464: 시스템 서비스 직접 접근을 추상화하면 테스트가 쉬워집니다.

Connectivity 체크를 작은 인터페이스(예: NetworkStatusProvider)를 통해 주입하면 단위 테스트에서 네트워크 상태를 쉽게 모의할 수 있습니다. 현재 방식도 동작상 문제는 없습니다.

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

8-16: 새 플로우·상태 노출 설계 적절 — 간단한 KDoc 보강 제안

에폭일(Long) 기반 날짜, 가시성 관련 Boolean 플로우, 그리고 FortuneCreateStatus 노출 모두 방향 좋습니다. 각 플로우/상태의 의미(초기값, 리셋 타이밍, 스레드 안전/원자성 기대)를 KDoc으로 명시하면 사용처 혼동을 줄일 수 있습니다.

data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (2)

27-36: 메서드 네이밍 일관성 소폭 개선 제안

리포지토리 레이어는 markFortuneAsCreating/…AsFailed, 로컬 레이어는 markFortuneCreating/…Failed로 접두어가 약간 다릅니다. 통일하면 호출부 가독성이 좋아집니다. 변경 범위가 넓다면 유지해도 무방합니다.


40-46: 원격 응답 매핑의 예외 안전성 확보 LGTM — 에러 도메인화는 차기 과제로

.mapCatching { it.toDomain() }로 매핑 단계 예외가 안전하게 포착됩니다. 향후 필요 시 네트워크/파싱 오류를 도메인 오류로 매핑하는 계층(예: Result.failure(DomainError.Network))을 추가하는 것을 고려해 볼 수 있습니다.

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

18-20: 철자 통일 제안: Tooltip

shouldShowFortuneToolTipFlowToolTip 표기가 일반적으로는 Tooltip으로 쓰입니다. 전역 검색 후 리네이밍을 고려해 주세요(낙타표기 유지 시 shouldShowFortuneTooltipFlow).

📜 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 0c0c8a9 and a58c58d.

⛔ Files ignored due to path filters (1)
  • project.dot.png is excluded by !**/*.png
📒 Files selected for processing (21)
  • app/src/main/AndroidManifest.xml (3 hunks)
  • app/src/main/java/com/yapp/orbit/di/AppVersionModule.kt (1 hunks)
  • core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt (3 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/repositoryimpl/FortuneRepositoryImpl.kt (2 hunks)
  • data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt (1 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/snooze/AlarmSnoozeTimerViewModel.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/component/bottomsheet/UpdateNoticeBottomSheet.kt (1 hunks)
  • feature/home/src/main/res/values/strings.xml (2 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2 hunks)
  • gradle/libs.versions.toml (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/home/src/main/res/values/strings.xml
  • app/src/main/AndroidManifest.xml
  • gradle/libs.versions.toml
🧰 Additional context used
🧠 Learnings (2)
📚 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:

  • data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt
  • feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
📚 Learning: 2025-09-14T15:32:44.064Z
Learnt from: DongChyeon
PR: YAPP-Github/Orbit-Android#252
File: domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt:16-17
Timestamp: 2025-09-14T15:32:44.064Z
Learning: Fortune creation requests have at least a 1-minute gap between them in the Orbit Android app, making atomic guards for race condition prevention unnecessary in the fortune creation flow.

Applied to files:

  • data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt
🧬 Code graph analysis (4)
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)
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)
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)
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 (50-134)
⏰ 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 (17)
feature/home/build.gradle.kts (1)

22-22: Coil Compose 의존성 추가 LGTM

Home에서 배너 이미지 로딩 용도라면 이 추가만으로 충분합니다.

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

11-18: 버전 주입 모듈 LGTM

BuildConfig.VERSION_NAME를 DI로 노출하는 접근 적절합니다.


17-17: 디버그 suffix 포함 여부 확인 필요

VERSION_NAMEversionNameSuffix가 붙는 빌드 변형이 있다면, UI/로깅/원격 구성에서 원하는 값이 맞는지 확인해 주세요.

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

18-23: 상태 플로우의 안정성(idempotency, distinctUntilChanged) 확인

UI thrash 방지를 위해 각 mutator가 idemponent하게 상태를 기록하고, fortuneCreateStatusFlow는 내부에서 distinctUntilChanged()가 적용되는지 확인 부탁드립니다.

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

146-161: 운세 생성 시 상태 플래그 초기화 로직 적절.

오늘 신규 운세 여부(isNewForToday) 판정 후 SEEN/TOOLTIP_SHOWN 초기화 방식이 기대 동작과 부합합니다. 👍

feature/home/src/main/java/com/yapp/home/HomeContract.kt (1)

62-64: 새 액션 추가 적절.

OnClickDontShowAgain / HideUpdateNotice 도입이 UI 플로우와 일치합니다.

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

11-13: 도메인 계층에 업데이트 공지 관리 API 노출 적절.

두 개의 Flow와 두 개의 변경 함수 추가 방향성 문제 없습니다. 구현/DI 일치 여부만 최종 확인 부탁드립니다.

Also applies to: 17-18

data/src/main/java/com/yapp/data/repositoryimpl/UserInfoRepositoryImpl.kt (1)

20-21: 위임 구현 문제 없음.

LocalDataSource에 올바르게 위임하고 있습니다.

Also applies to: 26-27

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

14-16: UserPreferences 위임 매핑 정확.

업데이트 공지 관련 Flow/Mutator 위임이 일관됩니다.

Also applies to: 29-35

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

416-422: API 35 이상에서만 인셋 보정 적용 로직 확인 필요.

sheetHalfExpandHeight = screenHeight - contentHeight - offset 계산에서, offset을 35+에서만 적용하도록 한 근거가 명확하면 OK이나, 디바이스/제스처 내비 조합에 따라 음수/비정상 값 가능성 있습니다. 실제 기기(API 33/34/35)로 반쯤 펼침 높이가 의도대로 보이는지 점검 부탁드립니다.


250-258: UpdateNoticeBottomSheet 연동 LGTM.

콜백과 액션 매핑이 명확합니다.


413-422: 컴파일 오류: Density 컨텍스트 없이 toDp() 호출.

placeable.height.toDp()는 Density 컨텍스트가 필요합니다. 아래처럼 수정하세요.

@@
-                                val contentHeight = placeable.height.toDp()
+                                val density = LocalDensity.current
+                                val contentHeight = with(density) { placeable.height.toDp() }

Likely an incorrect or invalid review comment.

feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (1)

378-405: 오프라인 시 업데이트 공지 무조건 숨김 정책 확인 필요.

배너 이미지만 실패해도 텍스트/버튼으로 공지를 노출하고 싶을 수 있습니다. 현재는 isOnlineNow()==false면 바로 return으로 숨깁니다. 기획 의도 확인 부탁드립니다(오프라인에서도 노출해야 한다면 이미지 로딩 실패 시 대체 UI로 전환).

data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (1)

18-26: 데이터 소스 위임·플로우 표면 재정렬 LGTM

로컬 데이터 소스의 새 플로우/상태를 그대로 노출하는 구성 깔끔합니다. 네이밍과 도메인 리포지토리 시그니처가 일치합니다.

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

38-55: 변이 위임 LGTM — 생성/성공 시 플래그 정합성 확인

모든 변이 메서드가 UserPreferences로 일관되게 위임되어 좋습니다. markFortuneCreatedfortuneId/fortuneDateEpoch 설정과 함께 isFortuneCreating=false, isFortuneFailed=false까지 한 트랜잭션에서 반영되는지 한번만 확인 부탁드립니다(중간 상태 노출 방지).

동일 검증은 상위 파일 코멘트의 스크립트로 확인 가능합니다.


66-72: 정리 메서드 위임 LGTM

markFirstAlarmDismissedTodayclearFortuneData 위임 모두 일관적입니다.

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

17-27: 변이 메서드 원자성(트랜잭션) 확인 요청

markFortuneCreating / markFortuneCreated / markFortuneFailed / markFortuneSeen / markFortuneTooltipShown / clearFortuneData가 UserPreferences(DataStore) 내 관련 키들을 반드시 단일 edit { } 블록(원자적 트랜잭션)으로 함께 갱신하는지 확인하세요. 중간 상태 불일치가 Flow 결합으로 UI 깜박임을 유발합니다.

제공하신 검증 스크립트가 파일을 검색하지 못했습니다. 리포지토리 루트에서 아래 스크립트를 실행한 출력 결과를 여기에 붙여 제출하세요.

#!/bin/bash
set -euo pipefail
echo "검증 시작: fortune 관련 변이 메서드와 DataStore edit 블록 검색"
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+markFortuneCreated\(' || true
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+markFortuneCreating\(' || true
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+markFortuneFailed\(' || true
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+markFortuneSeen\(' || true
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+markFortuneTooltipShown\(' || true
rg -nC5 -g '!**/build/**' -g '**/*.kt' '\bfun\s+clearFortuneData\(' || true
rg -nC3 -g '!**/build/**' -g '**/*.kt' 'edit\s*\{' || true
rg -nC3 -g '!**/build/**' -g '**/*.kt' 'isFortuneCreating|isFortuneFailed|fortuneId|fortuneDateEpoch|fortuneTooltipShown|fortuneSeen|saveFortuneImageId|saveFortuneScore|markFirstAlarmDismissedToday' || true

@DongChyeon DongChyeon merged commit eb9dc91 into main Sep 16, 2025
2 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.

3 participants