From 9d6b45ab9faac0ea4b1433ef4f27def38eae13cd Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 14:51:21 +0900 Subject: [PATCH 01/28] =?UTF-8?q?[REFACTOR/#246]=20Fortune=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20DataStore=20=EC=83=81=ED=83=9C=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/datastore/UserPreferences.kt | 164 +++++++++++------- .../local/datasource/AlarmLocalDataSource.kt | 4 - .../datasource/AlarmLocalDataSourceImpl.kt | 12 -- .../datasource/FortuneLocalDataSource.kt | 22 ++- .../datasource/FortuneLocalDataSourceImpl.kt | 39 +++-- .../repositoryimpl/AlarmRepositoryImpl.kt | 10 -- .../repositoryimpl/FortuneRepositoryImpl.kt | 29 ++-- .../yapp/domain/repository/AlarmRepository.kt | 4 - .../domain/repository/FortuneRepository.kt | 23 ++- 9 files changed, 175 insertions(+), 132 deletions(-) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index 35298556..3a338739 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -26,15 +26,22 @@ class UserPreferences @Inject constructor( val USER_ID = longPreferencesKey("user_id") val USER_NAME = stringPreferencesKey("user_name") val ONBOARDING_COMPLETED = booleanPreferencesKey("onboarding_completed") + val FORTUNE_ID = longPreferencesKey("fortune_id") val FORTUNE_DATE = stringPreferencesKey("fortune_date") val FORTUNE_IMAGE_ID = intPreferencesKey("fortune_image_id") val FORTUNE_SCORE = intPreferencesKey("fortune_score") - val FORTUNE_CHECKED = booleanPreferencesKey("fortune_checked") + val FORTUNE_SEEN = booleanPreferencesKey("fortune_seen") + val FORTUNE_TOOLTIP_SHOWN = booleanPreferencesKey("fortune_tooltip_shown") + val FORTUNE_CREATING = booleanPreferencesKey("fortune_creating") + val FORTUNE_FAILED = booleanPreferencesKey("fortune_failed") + val FIRST_DISMISSED_ALARM_ID = longPreferencesKey("first_dismissed_alarm_id") val DISMISSED_DATE = stringPreferencesKey("dismissed_date") } + private fun today(): String = LocalDate.now().format(DateTimeFormatter.ISO_DATE) + val userIdFlow: Flow = dataStore.data .catch { emit(emptyPreferences()) } .map { it[Keys.USER_ID] } @@ -70,108 +77,139 @@ class UserPreferences @Inject constructor( .map { it[Keys.FORTUNE_SCORE] } .distinctUntilChanged() - val hasNewFortuneFlow: Flow = dataStore.data + val hasUnseenFortuneFlow: Flow = dataStore.data + .map { pref -> + pref[Keys.FORTUNE_DATE] == today() && + pref[Keys.FORTUNE_ID] != null && + (pref[Keys.FORTUNE_SEEN] != true) + } + .distinctUntilChanged() + + val shouldShowFortuneToolTipFlow: Flow = dataStore.data .catch { emit(emptyPreferences()) } - .map { preferences -> - val savedDate = preferences[Keys.FORTUNE_DATE] - val isChecked = preferences[Keys.FORTUNE_CHECKED] ?: true - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - savedDate == todayDate && !isChecked + .map { pref -> + val hasTodayFortune = pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null + val tooltipNotShown = pref[Keys.FORTUNE_TOOLTIP_SHOWN] ?: true + hasTodayFortune && tooltipNotShown } .distinctUntilChanged() - val firstDismissedAlarmIdFlow: Flow = dataStore.data + val isFortuneCreatingFlow: Flow = dataStore.data .catch { emit(emptyPreferences()) } - .map { preferences -> - val savedDate = preferences[Keys.DISMISSED_DATE] - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - - if (savedDate == todayDate) { - preferences[Keys.FIRST_DISMISSED_ALARM_ID] - } else { - null - } + .map { it[Keys.FORTUNE_CREATING] ?: false } + .distinctUntilChanged() + + val isFortuneFailedFlow: Flow = dataStore.data + .catch { emit(emptyPreferences()) } + .map { it[Keys.FORTUNE_FAILED] ?: false } + .distinctUntilChanged() + + val isFirstAlarmDismissedTodayFlow: Flow = dataStore.data + .catch { emit(emptyPreferences()) } + .map { pref -> + pref[Keys.DISMISSED_DATE] == today() && pref[Keys.FIRST_DISMISSED_ALARM_ID] != null } .distinctUntilChanged() suspend fun saveUserId(userId: Long) { - dataStore.edit { preferences -> - preferences[Keys.USER_ID] = userId + dataStore.edit { pref -> + pref[Keys.USER_ID] = userId } } suspend fun saveUserName(userName: String) { - dataStore.edit { preferences -> - preferences[Keys.USER_NAME] = userName + dataStore.edit { pref -> + pref[Keys.USER_NAME] = userName } } - suspend fun saveFortuneId(fortuneId: Long) { - val currentDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - dataStore.edit { preferences -> - preferences[Keys.FORTUNE_ID] = fortuneId - preferences[Keys.FORTUNE_DATE] = currentDate - preferences[Keys.FORTUNE_CHECKED] = false + suspend fun setOnboardingCompleted() { + dataStore.edit { pref -> + pref[Keys.ONBOARDING_COMPLETED] = true } } - suspend fun markFortuneAsChecked() { - dataStore.edit { preferences -> - preferences[Keys.FORTUNE_CHECKED] = true + suspend fun markFortuneCreating() { + dataStore.edit { pref -> + pref[Keys.FORTUNE_CREATING] = true + pref[Keys.FORTUNE_FAILED] = false } } - suspend fun saveFortuneImageId(imageResId: Int) { - dataStore.edit { preferences -> - preferences[Keys.FORTUNE_IMAGE_ID] = imageResId + suspend fun markFortuneCreated(fortuneId: Long) { + dataStore.edit { pref -> + pref[Keys.FORTUNE_ID] = fortuneId + pref[Keys.FORTUNE_DATE] = today() + pref[Keys.FORTUNE_CREATING] = false + pref[Keys.FORTUNE_FAILED] = false + pref[Keys.FORTUNE_SEEN] = false + pref[Keys.FORTUNE_TOOLTIP_SHOWN] = false } } - suspend fun saveFortuneScore(score: Int) { - dataStore.edit { preferences -> - preferences[Keys.FORTUNE_SCORE] = score + suspend fun markFortuneFailed() { + dataStore.edit { pref -> + pref[Keys.FORTUNE_CREATING] = false + pref[Keys.FORTUNE_FAILED] = true } } - suspend fun saveFirstDismissedAlarmId(alarmId: Long) { - dataStore.edit { preferences -> - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - if (preferences[Keys.FIRST_DISMISSED_ALARM_ID] == null) { - preferences[Keys.FIRST_DISMISSED_ALARM_ID] = alarmId - preferences[Keys.DISMISSED_DATE] = todayDate - Log.d("UserPreferences", "첫 해제된 알람 ID 저장 완료: $alarmId (날짜: $todayDate)") - } else { - Log.d("UserPreferences", "이미 첫 알람 해제 ID가 저장되어 있음)") - } + suspend fun markFortuneSeen() { + dataStore.edit { pref -> + pref[Keys.FORTUNE_SEEN] = true } } - suspend fun setOnboardingCompleted() { - dataStore.edit { preferences -> - preferences[Keys.ONBOARDING_COMPLETED] = true + suspend fun markFortuneTooltipShown() { + dataStore.edit { pref -> + pref[Keys.FORTUNE_TOOLTIP_SHOWN] = true } } - suspend fun clearDismissedAlarmId() { - dataStore.edit { preferences -> - preferences.remove(Keys.FIRST_DISMISSED_ALARM_ID) - preferences.remove(Keys.DISMISSED_DATE) + suspend fun saveFortuneImageId(imageResId: Int) { + dataStore.edit { pref -> + pref[Keys.FORTUNE_IMAGE_ID] = imageResId } } - suspend fun clearUserData() { - dataStore.edit { preferences -> - preferences.clear() + suspend fun saveFortuneScore(score: Int) { + dataStore.edit { pref -> + pref[Keys.FORTUNE_SCORE] = score } } - suspend fun clearFortuneId() { - dataStore.edit { preferences -> - preferences.remove(Keys.FORTUNE_ID) - preferences.remove(Keys.FORTUNE_DATE) - preferences.remove(Keys.FORTUNE_IMAGE_ID) - preferences.remove(Keys.FORTUNE_SCORE) - preferences.remove(Keys.FORTUNE_CHECKED) + suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) { + dataStore.edit { pref -> + if (pref[Keys.DISMISSED_DATE] == today() && pref[Keys.FIRST_DISMISSED_ALARM_ID] != null) { + Log.d("UserPreferences", "이미 오늘 첫 알람 해제 기록이 있음") + return@edit + } + pref[Keys.FIRST_DISMISSED_ALARM_ID] = firstAlarmId + pref[Keys.DISMISSED_DATE] = today() + } + } + + suspend fun clearFirstAlarmDismissedToday() { + dataStore.edit { pref -> + pref.remove(Keys.FIRST_DISMISSED_ALARM_ID) + pref.remove(Keys.DISMISSED_DATE) + } + } + + suspend fun clearUserData() { + dataStore.edit { pref -> pref.clear() } + } + + suspend fun clearFortuneData() { + dataStore.edit { pref -> + pref.remove(Keys.FORTUNE_ID) + pref.remove(Keys.FORTUNE_DATE) + pref.remove(Keys.FORTUNE_IMAGE_ID) + pref.remove(Keys.FORTUNE_SCORE) + pref.remove(Keys.FORTUNE_SEEN) + pref.remove(Keys.FORTUNE_TOOLTIP_SHOWN) + pref.remove(Keys.FORTUNE_CREATING) + pref.remove(Keys.FORTUNE_FAILED) } } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt index eb9fb350..8ff592d2 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSource.kt @@ -5,8 +5,6 @@ import com.yapp.domain.model.Alarm import kotlinx.coroutines.flow.Flow interface AlarmLocalDataSource { - val firstDismissedAlarmIdFlow: Flow - fun getAllAlarms(): Flow> fun getAlarmsByTime(hour: Int, minute: Int): Flow> suspend fun insertAlarm(alarm: AlarmEntity): Long @@ -14,6 +12,4 @@ interface AlarmLocalDataSource { suspend fun updateAlarmActive(id: Long, active: Boolean): Int suspend fun getAlarm(id: Long): Alarm? suspend fun deleteAlarm(id: Long): Int - suspend fun saveFirstDismissedAlarmId(alarmId: Long) - suspend fun clearDismissedAlarmId() } diff --git a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt index 7c7425b2..03fecd55 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/AlarmLocalDataSourceImpl.kt @@ -3,7 +3,6 @@ package com.yapp.data.local.datasource import com.yapp.database.AlarmDao import com.yapp.database.AlarmEntity import com.yapp.database.toDomain -import com.yapp.datastore.UserPreferences import com.yapp.domain.model.Alarm import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -11,10 +10,7 @@ import javax.inject.Inject class AlarmLocalDataSourceImpl @Inject constructor( private val alarmDao: AlarmDao, - private val userPreferences: UserPreferences, ) : AlarmLocalDataSource { - override val firstDismissedAlarmIdFlow: Flow = userPreferences.firstDismissedAlarmIdFlow - override fun getAllAlarms(): Flow> { return alarmDao.getAllAlarms() .map { alarmEntities -> alarmEntities.map { it.toDomain() } } @@ -45,12 +41,4 @@ class AlarmLocalDataSourceImpl @Inject constructor( override suspend fun deleteAlarm(id: Long): Int { return alarmDao.deleteAlarm(id) } - - override suspend fun saveFirstDismissedAlarmId(alarmId: Long) { - userPreferences.saveFirstDismissedAlarmId(alarmId) - } - - override suspend fun clearDismissedAlarmId() { - userPreferences.clearDismissedAlarmId() - } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt index 149234f7..5a276483 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt @@ -7,14 +7,22 @@ interface FortuneLocalDataSource { val fortuneDateFlow: Flow val fortuneImageIdFlow: Flow val fortuneScoreFlow: Flow - val hasNewFortuneFlow: Flow - val firstDismissedAlarmIdFlow: Flow + val hasUnseenFortuneFlow: Flow + val shouldShowFortuneToolTipFlow: Flow + val isFirstAlarmDismissedTodayFlow: Flow + val isFortuneCreatingFlow: Flow + val isFortuneFailedFlow: Flow - suspend fun saveFortuneId(fortuneId: Long) - suspend fun markFortuneAsChecked() + suspend fun markFortuneCreating() + suspend fun markFortuneCreated(fortuneId: Long) + suspend fun markFortuneFailed() + suspend fun markFortuneSeen() + suspend fun markFortuneTooltipShown() suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) - suspend fun saveFirstDismissedAlarmId(alarmId: Long) - suspend fun clearDismissedAlarmId() - suspend fun clearFortuneId() + + suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) + suspend fun clearFirstAlarmDismissedToday() + + suspend fun clearFortuneData() } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt index 6dbe10f9..31f17f7a 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt @@ -11,15 +11,30 @@ class FortuneLocalDataSourceImpl @Inject constructor( override val fortuneDateFlow = userPreferences.fortuneDateFlow override val fortuneImageIdFlow = userPreferences.fortuneImageIdFlow override val fortuneScoreFlow = userPreferences.fortuneScoreFlow - override val hasNewFortuneFlow = userPreferences.hasNewFortuneFlow - override val firstDismissedAlarmIdFlow = userPreferences.firstDismissedAlarmIdFlow + override val hasUnseenFortuneFlow = userPreferences.hasUnseenFortuneFlow + override val shouldShowFortuneToolTipFlow = userPreferences.shouldShowFortuneToolTipFlow + override val isFirstAlarmDismissedTodayFlow = userPreferences.isFirstAlarmDismissedTodayFlow + override val isFortuneCreatingFlow = userPreferences.isFortuneCreatingFlow + override val isFortuneFailedFlow = userPreferences.isFortuneFailedFlow + + override suspend fun markFortuneCreating() { + userPreferences.markFortuneCreating() + } + + override suspend fun markFortuneCreated(fortuneId: Long) { + userPreferences.markFortuneCreated(fortuneId) + } + + override suspend fun markFortuneFailed() { + userPreferences.markFortuneFailed() + } - override suspend fun saveFortuneId(fortuneId: Long) { - userPreferences.saveFortuneId(fortuneId) + override suspend fun markFortuneSeen() { + userPreferences.markFortuneSeen() } - override suspend fun markFortuneAsChecked() { - userPreferences.markFortuneAsChecked() + override suspend fun markFortuneTooltipShown() { + userPreferences.markFortuneTooltipShown() } override suspend fun saveFortuneImageId(imageResId: Int) { @@ -30,15 +45,15 @@ class FortuneLocalDataSourceImpl @Inject constructor( userPreferences.saveFortuneScore(score) } - override suspend fun saveFirstDismissedAlarmId(alarmId: Long) { - userPreferences.saveFirstDismissedAlarmId(alarmId) + override suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) { + userPreferences.saveFirstAlarmDismissedToday(firstAlarmId) } - override suspend fun clearDismissedAlarmId() { - userPreferences.clearDismissedAlarmId() + override suspend fun clearFirstAlarmDismissedToday() { + userPreferences.clearFirstAlarmDismissedToday() } - override suspend fun clearFortuneId() { - userPreferences.clearFortuneId() + override suspend fun clearFortuneData() { + userPreferences.clearFortuneData() } } diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt index a1c72135..50385b4f 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/AlarmRepositoryImpl.kt @@ -16,8 +16,6 @@ class AlarmRepositoryImpl @Inject constructor( private val ringtoneManagerHelper: RingtoneManagerHelper, private val soundPlayer: SoundPlayer, ) : AlarmRepository { - override val firstDismissedAlarmIdFlow: Flow = alarmLocalDataSource.firstDismissedAlarmIdFlow - override suspend fun getAlarmSounds(): Result> = runCatching { ringtoneManagerHelper.getAlarmSounds().map { (title, uri) -> AlarmSound(title, uri) @@ -93,12 +91,4 @@ class AlarmRepositoryImpl @Inject constructor( throw Exception("No rows deleted") } } - - override suspend fun saveFirstDismissedAlarmId(alarmId: Long) { - alarmLocalDataSource.saveFirstDismissedAlarmId(alarmId) - } - - override suspend fun clearDismissedAlarmId() { - alarmLocalDataSource.clearDismissedAlarmId() - } } diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt index d3abb0e9..444e9eae 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt @@ -12,32 +12,35 @@ class FortuneRepositoryImpl @Inject constructor( private val fortuneLocalDataSource: FortuneLocalDataSource, private val fortuneRemoteDataSource: FortuneDataSource, ) : FortuneRepository { + override val fortuneIdFlow: Flow = fortuneLocalDataSource.fortuneIdFlow override val fortuneDateFlow: Flow = fortuneLocalDataSource.fortuneDateFlow override val fortuneImageIdFlow: Flow = fortuneLocalDataSource.fortuneImageIdFlow override val fortuneScoreFlow: Flow = fortuneLocalDataSource.fortuneScoreFlow - override val hasNewFortuneFlow: Flow = fortuneLocalDataSource.hasNewFortuneFlow - override val firstDismissedAlarmIdFlow: Flow = fortuneLocalDataSource.firstDismissedAlarmIdFlow + override val hasUnseenFortuneFlow: Flow = fortuneLocalDataSource.hasUnseenFortuneFlow + override val shouldShowFortuneToolTipFlow: Flow = fortuneLocalDataSource.shouldShowFortuneToolTipFlow + override val isFirstAlarmDismissedTodayFlow: Flow = fortuneLocalDataSource.isFirstAlarmDismissedTodayFlow + override val isFortuneCreatingFlow: Flow = fortuneLocalDataSource.isFortuneCreatingFlow + override val isFortuneFailedFlow: Flow = fortuneLocalDataSource.isFortuneFailedFlow - override suspend fun saveFortuneId(fortuneId: Long) = fortuneLocalDataSource.saveFortuneId(fortuneId) - override suspend fun markFortuneAsChecked() = fortuneLocalDataSource.markFortuneAsChecked() + override suspend fun markFortuneAsCreating() = fortuneLocalDataSource.markFortuneCreating() + override suspend fun markFortuneAsCreated(fortuneId: Long) = fortuneLocalDataSource.markFortuneCreated(fortuneId) + override suspend fun markFortuneAsFailed() = fortuneLocalDataSource.markFortuneFailed() + override suspend fun markFortuneSeen() = fortuneLocalDataSource.markFortuneSeen() + override suspend fun markFortuneTooltipShown() = fortuneLocalDataSource.markFortuneTooltipShown() override suspend fun saveFortuneImageId(imageResId: Int) = fortuneLocalDataSource.saveFortuneImageId(imageResId) override suspend fun saveFortuneScore(score: Int) = fortuneLocalDataSource.saveFortuneScore(score) - override suspend fun saveFirstDismissedAlarmId(alarmId: Long) = fortuneLocalDataSource.saveFirstDismissedAlarmId(alarmId) - override suspend fun clearDismissedAlarmId() = fortuneLocalDataSource.clearDismissedAlarmId() - override suspend fun clearFortuneId() = fortuneLocalDataSource.clearFortuneId() + override suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) = fortuneLocalDataSource.saveFirstAlarmDismissedToday(firstAlarmId) + override suspend fun clearFirstAlarmDismissedToday() = fortuneLocalDataSource.clearFirstAlarmDismissedToday() + override suspend fun clearFortuneData() = fortuneLocalDataSource.clearFortuneData() override suspend fun postFortune(userId: Long): Result { return fortuneRemoteDataSource.postFortune(userId) - .mapCatching { fortuneResponse -> - fortuneResponse.toDomain() - } + .mapCatching { it.toDomain() } } override suspend fun getFortune(fortuneId: Long): Result { return fortuneRemoteDataSource.getFortune(fortuneId) - .mapCatching { fortuneResponse -> - fortuneResponse.toDomain() - } + .mapCatching { it.toDomain() } } } diff --git a/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt b/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt index f7dac361..60123473 100644 --- a/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/AlarmRepository.kt @@ -6,8 +6,6 @@ import com.yapp.domain.model.AlarmSound import kotlinx.coroutines.flow.Flow interface AlarmRepository { - val firstDismissedAlarmIdFlow: Flow - suspend fun getAlarmSounds(): Result> fun initializeSoundPlayer(uri: Uri) fun playAlarmSound(volume: Int) @@ -21,6 +19,4 @@ interface AlarmRepository { suspend fun updateAlarmActive(id: Long, active: Boolean): Result suspend fun getAlarm(id: Long): Result suspend fun deleteAlarm(id: Long): Result - suspend fun saveFirstDismissedAlarmId(alarmId: Long) - suspend fun clearDismissedAlarmId() } diff --git a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt index 23598e3c..a6d346b8 100644 --- a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt @@ -8,16 +8,25 @@ interface FortuneRepository { val fortuneDateFlow: Flow val fortuneImageIdFlow: Flow val fortuneScoreFlow: Flow - val hasNewFortuneFlow: Flow - val firstDismissedAlarmIdFlow: Flow + val hasUnseenFortuneFlow: Flow + val shouldShowFortuneToolTipFlow: Flow + val isFirstAlarmDismissedTodayFlow: Flow + val isFortuneCreatingFlow: Flow + val isFortuneFailedFlow: Flow - suspend fun saveFortuneId(fortuneId: Long) - suspend fun markFortuneAsChecked() + suspend fun markFortuneAsCreating() + suspend fun markFortuneAsCreated(fortuneId: Long) + suspend fun markFortuneAsFailed() + suspend fun markFortuneSeen() + suspend fun markFortuneTooltipShown() suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) - suspend fun saveFirstDismissedAlarmId(alarmId: Long) - suspend fun clearDismissedAlarmId() - suspend fun clearFortuneId() + + suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) + suspend fun clearFirstAlarmDismissedToday() + + suspend fun clearFortuneData() + suspend fun postFortune(userId: Long): Result suspend fun getFortune(fortuneId: Long): Result } From 16f5dd7b4f5151735eb4f9a5438e8711643f9018 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 14:52:47 +0900 Subject: [PATCH 02/28] =?UTF-8?q?[FEAT/#246]=20=EC=98=A4=EB=8A=98=EC=9D=98?= =?UTF-8?q?=20=EC=9A=B4=EC=84=B8=20=EB=B0=9C=EA=B8=89=20WorkManager=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 ++ app/src/main/AndroidManifest.xml | 6 +++ .../java/com/yapp/orbit/OrbitApplication.kt | 13 ++++- .../java/orbit.android.feature.gradle.kts | 3 -- .../scheduler/PostFortuneTaskScheduler.kt | 5 ++ feature/fortune/build.gradle.kts | 3 ++ .../com/yapp/fortune/di/SchedulerModule.kt | 19 +++++++ .../WorkManagerPostFortuneTaskScheduler.kt | 32 ++++++++++++ .../yapp/fortune/worker/PostFortuneWorker.kt | 51 +++++++++++++++++++ gradle/libs.versions.toml | 6 +++ 10 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt create mode 100644 feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt create mode 100644 feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt create mode 100644 feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d56d2894..3b5e9d8a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,6 +13,7 @@ android { versionCode = 6 versionName = "1.1.3" targetSdk = 35 + compileSdk = 35 } buildTypes { @@ -53,4 +54,6 @@ dependencies { implementation(libs.firebase.crashlytics) implementation(libs.play.services.ads) implementation(libs.kotlin.reflect) + implementation(libs.hilt.worker) + implementation(libs.androidx.work.runtime) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 780b61bb..95ede913 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,5 +80,11 @@ + + + diff --git a/app/src/main/java/com/yapp/orbit/OrbitApplication.kt b/app/src/main/java/com/yapp/orbit/OrbitApplication.kt index 7391cf6e..b06538f8 100644 --- a/app/src/main/java/com/yapp/orbit/OrbitApplication.kt +++ b/app/src/main/java/com/yapp/orbit/OrbitApplication.kt @@ -1,13 +1,24 @@ package com.yapp.orbit import android.app.Application +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration import com.google.android.gms.ads.MobileAds import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject @HiltAndroidApp -class OrbitApplication : Application() { +class OrbitApplication() : Application(), Configuration.Provider { + + @Inject lateinit var workerFactory: HiltWorkerFactory + override fun onCreate() { super.onCreate() MobileAds.initialize(this) } + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() } diff --git a/build-logic/src/main/java/orbit.android.feature.gradle.kts b/build-logic/src/main/java/orbit.android.feature.gradle.kts index aa5803c4..47d5c072 100644 --- a/build-logic/src/main/java/orbit.android.feature.gradle.kts +++ b/build-logic/src/main/java/orbit.android.feature.gradle.kts @@ -1,4 +1,3 @@ -import com.yapp.convention.configureHiltAndroid import com.yapp.convention.libs plugins { @@ -6,8 +5,6 @@ plugins { id("orbit.android.compose") } -configureHiltAndroid() - dependencies { implementation(project(":core:designsystem")) implementation(project(":core:ui")) diff --git a/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt b/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt new file mode 100644 index 00000000..e16e8dd4 --- /dev/null +++ b/core/alarm/src/main/java/com/yapp/alarm/scheduler/PostFortuneTaskScheduler.kt @@ -0,0 +1,5 @@ +package com.yapp.alarm.scheduler + +interface PostFortuneTaskScheduler { + fun enqueueOnceForToday() +} diff --git a/feature/fortune/build.gradle.kts b/feature/fortune/build.gradle.kts index ae450155..533e41d5 100644 --- a/feature/fortune/build.gradle.kts +++ b/feature/fortune/build.gradle.kts @@ -12,10 +12,13 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.common) implementation(projects.core.analytics) + implementation(projects.core.alarm) implementation(libs.orbit.core) implementation(libs.orbit.compose) implementation(libs.orbit.viewmodel) implementation(libs.coil.compose) + implementation(libs.androidx.work.runtime) + implementation(libs.androidx.work.testing) implementation(projects.domain) implementation(projects.core.media) } diff --git a/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt b/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt new file mode 100644 index 00000000..47fbd0b0 --- /dev/null +++ b/feature/fortune/src/main/java/com/yapp/fortune/di/SchedulerModule.kt @@ -0,0 +1,19 @@ +package com.yapp.fortune.di + +import com.yapp.alarm.scheduler.PostFortuneTaskScheduler +import com.yapp.fortune.scheduler.WorkManagerPostFortuneTaskScheduler +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class SchedulerModule { + @Binds + @Singleton + abstract fun bindsPostFortuneTaskScheduler( + postFortuneTaskScheduler: WorkManagerPostFortuneTaskScheduler, + ): PostFortuneTaskScheduler +} diff --git a/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt b/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt new file mode 100644 index 00000000..36e49c4f --- /dev/null +++ b/feature/fortune/src/main/java/com/yapp/fortune/scheduler/WorkManagerPostFortuneTaskScheduler.kt @@ -0,0 +1,32 @@ +package com.yapp.fortune.scheduler + +import android.content.Context +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import com.yapp.alarm.scheduler.PostFortuneTaskScheduler +import com.yapp.fortune.worker.PostFortuneWorker +import dagger.hilt.android.qualifiers.ApplicationContext +import java.time.LocalDate +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class WorkManagerPostFortuneTaskScheduler @Inject constructor( + @ApplicationContext private val context: Context, +) : PostFortuneTaskScheduler { + override fun enqueueOnceForToday() { + val name = "post_fortune_${LocalDate.now()}" + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + val req = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.SECONDS) + .build() + WorkManager.getInstance(context) + .enqueueUniqueWork(name, ExistingWorkPolicy.KEEP, req) + } +} diff --git a/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt new file mode 100644 index 00000000..a3b3e1ca --- /dev/null +++ b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt @@ -0,0 +1,51 @@ +package com.yapp.fortune.worker + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.yapp.domain.repository.FortuneRepository +import com.yapp.domain.repository.UserInfoRepository +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull + +@HiltWorker +class PostFortuneWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted params: WorkerParameters, + private val fortuneRepository: FortuneRepository, + private val userInfoRepository: UserInfoRepository, +) : CoroutineWorker(appContext, params) { + + override suspend fun doWork(): Result { + val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() + val creating = fortuneRepository.isFortuneCreatingFlow.first() + + if (hasUnseenFortune || creating) { + return Result.success() + } + + val userId = userInfoRepository.userIdFlow.firstOrNull() ?: return Result.failure() + + return try { + fortuneRepository.markFortuneAsCreating() + val result = fortuneRepository.postFortune(userId) + result.fold( + onSuccess = { fortune -> + fortuneRepository.markFortuneAsCreated(fortune.id) + fortuneRepository.saveFortuneScore(fortune.avgFortuneScore) + Result.success() + }, + onFailure = { e -> + fortuneRepository.markFortuneAsFailed() + Result.retry() + }, + ) + } catch (_: Throwable) { + fortuneRepository.markFortuneAsFailed() + Result.retry() + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c10bb87c..db4817c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,6 +29,7 @@ androidx-app-compat = "1.7.0" androidx-core = "1.15.0" androidx-datastore = "1.1.1" androidx-room = "2.7.2" +androidx-work = "2.10.3" androidx-lifecycle = "2.8.7" @@ -44,6 +45,7 @@ activity-compose = "1.9.3" ## Hilt hilt = "2.51.1" hilt-navigation-compose = "1.2.0" +hilt-work = "1.2.0" ## Third Party okhttp = "4.12.0" @@ -102,6 +104,9 @@ androidx-room-compiler = { group = "androidx.room", name = "room-compiler", vers androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "androidx-room" } androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "androidx-room" } androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" } +androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hilt-work" } +androidx-work-runtime = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidx-work" } +androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "androidx-work" } ## Compose Libraries activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } @@ -129,6 +134,7 @@ hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" } +hilt-worker = { group = "androidx.hilt", name = "hilt-work", version.ref = "hilt-work" } # Orbit orbit-core = { group = "org.orbit-mvi", name = "orbit-core", version.ref = "orbit" } From 290f07a3b8a7bced9ed7d4f6d844000169f3ba1f Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:07:07 +0900 Subject: [PATCH 03/28] =?UTF-8?q?[FEAT/#246]=20=EC=95=8C=EB=9E=8C=EC=9D=B4?= =?UTF-8?q?=20=EC=9A=B8=EB=A6=B4=20=EB=95=8C=20FortuneTaskScheduler?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=B4=EC=84=9C=20=EC=98=A4?= =?UTF-8?q?=EB=8A=98=EC=9D=98=20=EC=9A=B4=EC=84=B8=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=98=88=EC=95=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/services/AlarmService.kt | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt b/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt index 96596729..56141679 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt @@ -23,10 +23,10 @@ import com.yapp.alarm.pendingIntent.interaction.createAlarmAlertPendingIntent import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissPendingIntent import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozePendingIntent import com.yapp.alarm.pendingIntent.interaction.createNavigateToMissionPendingIntent +import com.yapp.alarm.scheduler.PostFortuneTaskScheduler import com.yapp.domain.model.Alarm import com.yapp.domain.model.AlarmDay import com.yapp.domain.model.MissionType -import com.yapp.domain.repository.FortuneRepository import com.yapp.domain.usecase.AlarmUseCase import com.yapp.media.sound.SoundPlayer import dagger.hilt.android.AndroidEntryPoint @@ -34,10 +34,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import java.time.LocalDate -import java.time.format.DateTimeFormatter import javax.inject.Inject @AndroidEntryPoint @@ -55,7 +52,7 @@ class AlarmService : Service() { lateinit var androidAlarmScheduler: AndroidAlarmScheduler @Inject - lateinit var fortuneRepository: FortuneRepository + lateinit var postFortuneTaskScheduler: PostFortuneTaskScheduler private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -82,7 +79,7 @@ class AlarmService : Service() { super.onDestroy() } - private suspend fun handleIntent(intent: Intent) { + private fun handleIntent(intent: Intent) { val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java) } else { @@ -124,16 +121,14 @@ class AlarmService : Service() { if (isOneTimeAlarm) { turnOffAlarm(alarmId = notificationId) } + + postFortuneTaskScheduler.enqueueOnceForToday() } - private suspend fun shouldNavigateToMission( + private fun shouldNavigateToMission( missionType: MissionType, ): Boolean { - if (missionType == MissionType.NONE) return false - - val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull() - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - return fortuneDate != todayDate + return missionType != MissionType.NONE } private fun createNotification(alarm: Alarm, shouldNavigateToMission: Boolean): Notification { From 35c402c6219d79ee46d15e3b395504e2e2a9ec8b Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:09:40 +0900 Subject: [PATCH 04/28] =?UTF-8?q?[FEAT/#246]=20Hilt=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt b/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt index e266704f..15db09ff 100644 --- a/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt +++ b/build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt @@ -14,7 +14,9 @@ internal fun Project.configureHiltAndroid() { dependencies { "implementation"(libs.findLibrary("hilt.android").get()) "ksp"(libs.findLibrary("hilt.android.compiler").get()) + "ksp"(libs.findLibrary("androidx-hilt-compiler").get()) "implementation"(libs.findLibrary("hilt-navigation-compose").get()) + "implementation"(libs.findLibrary("hilt-worker").get()) } } From f57e976d91befe029afd8e4d0469520a2eb72786 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:19:08 +0900 Subject: [PATCH 05/28] =?UTF-8?q?[REMOVE/#246]=20=EC=9A=B4=EC=84=B8=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=97=AC=EB=B6=80=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EC=97=86=EC=9D=B4=20=EB=AF=B8=EC=85=98=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AlarmInteractionActivityReceiver.kt | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt index 39de3171..009e52c5 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt @@ -11,10 +11,7 @@ import com.yapp.domain.repository.FortuneRepository import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import java.time.LocalDate -import java.time.format.DateTimeFormatter import javax.inject.Inject @AndroidEntryPoint @@ -45,21 +42,14 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) if (!hasValidMissionData) return CoroutineScope(Dispatchers.IO).launch { - val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull() - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - - val shouldLaunchMission = fortuneDate != todayDate - - if (shouldLaunchMission) { - context?.let { - val uriString = - "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount" - val missionIntent = Intent(Intent.ACTION_VIEW, uriString.toUri()).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - setPackage(it.packageName) - } - it.startActivity(missionIntent) + context?.let { + val uriString = + "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount" + val missionIntent = Intent(Intent.ACTION_VIEW, uriString.toUri()).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage(it.packageName) } + it.startActivity(missionIntent) } } } From 1240faf93881cc63ff57f7c0f8b09d46df873ce5 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:24:17 +0900 Subject: [PATCH 06/28] =?UTF-8?q?[FEAT/#246]=20=EC=98=A4=EB=8A=98=20?= =?UTF-8?q?=EC=9A=B8=EB=A6=AC=EB=8A=94=20=EC=B2=AB=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20=EC=8B=9C=EC=97=90=EB=A7=8C=20=EC=9A=B4?= =?UTF-8?q?=EC=84=B8=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/receivers/AlarmReceiver.kt | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index edd18466..e1e3e562 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -19,8 +19,8 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import java.time.LocalDate import java.time.LocalDateTime import javax.inject.Inject @@ -113,9 +113,31 @@ class AlarmReceiver : BroadcastReceiver() { ) CoroutineScope(Dispatchers.IO).launch { - val alarms = alarmUseCase.getAllAlarms().first().sortedBy { it.isAlarmActive } - val isFirstAlarm = alarms.firstOrNull()?.id == notificationId + val alarms = alarmUseCase.getAllAlarms().first() + + val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET + if (!isSnoozeId) { + val today = LocalDate.now().dayOfWeek + fun Alarm.ringsToday(): Boolean { + if (repeatDays == 0) return true + return (repeatDays and (1 shl today.ordinal)) != 0 + } + + val earliestIdToday: Long? = alarms + .asSequence() + .filter { it.isAlarmActive && it.ringsToday() } + .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second })) + .firstOrNull() + ?.id + + val isEarliestAlarmDismissedToday = (earliestIdToday == notificationId) + + if (isEarliestAlarmDismissedToday) { + fortuneRepository.saveFirstAlarmDismissedToday(notificationId) + } + } + val isFirstAlarm = alarms.firstOrNull()?.id == notificationId analyticsHelper.logEvent( AnalyticsEvent( type = "alarm_dismiss", @@ -125,13 +147,6 @@ class AlarmReceiver : BroadcastReceiver() { ), ), ) - - val existingId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull() - if (existingId == null) { - fortuneRepository.saveFirstDismissedAlarmId(notificationId) - } else if (existingId != notificationId) { - fortuneRepository.clearDismissedAlarmId() - } } Toast.makeText(context, "알람이 해제되었어요", Toast.LENGTH_SHORT).show() From 9065182f814d612c3b287d92182e4aeb8ccbccd3 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:28:44 +0900 Subject: [PATCH 07/28] =?UTF-8?q?[FEAT/#246]=20=EC=95=8C=EB=9E=8C=EC=9D=98?= =?UTF-8?q?=20=EB=AF=B8=EC=85=98=20=ED=83=80=EC=9E=85=EC=9D=B4=20NONE?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=9D=BC=EB=A9=B4=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interaction/action/AlarmActionViewModel.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt index b4ca2c5c..bc5d81af 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt @@ -7,10 +7,8 @@ import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozeIntent import com.yapp.domain.model.Alarm import com.yapp.domain.model.MissionType -import com.yapp.domain.repository.FortuneRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.firstOrNull import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent @@ -19,7 +17,6 @@ import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container import java.time.LocalDate import java.time.LocalTime -import java.time.format.DateTimeFormatter import java.time.format.TextStyle import java.util.Locale import javax.inject.Inject @@ -27,7 +24,6 @@ import javax.inject.Inject @HiltViewModel class AlarmActionViewModel @Inject constructor( private val app: Application, - private val fortuneRepository: FortuneRepository, savedStateHandle: SavedStateHandle, ) : ViewModel(), ContainerHost { @@ -60,15 +56,8 @@ class AlarmActionViewModel @Inject constructor( } private fun fetchShouldShowMissionStart() = intent { - val missionType = alarm?.missionType - val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull() - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - val shouldShowMissionStart = missionType != null && - missionType != MissionType.NONE && - fortuneDate != todayDate - reduce { - state.copy(shouldShowMissionStart = shouldShowMissionStart) + state.copy(shouldShowMissionStart = alarm?.missionType != MissionType.NONE) } } From 38ca81e5e7195cff265448a6813ae2917d4e2cb7 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:30:23 +0900 Subject: [PATCH 08/28] =?UTF-8?q?[FEAT/#246]=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=A4=91=EC=9D=B4=EA=B1=B0=EB=82=98=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20?= =?UTF-8?q?=EC=9A=B4=EC=84=B8=EA=B0=80=20=EC=9E=88=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EC=97=90=EB=A7=8C=20=EC=9A=B4=EC=84=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/mission/MissionContract.kt | 1 - .../java/com/yapp/mission/MissionScreen.kt | 16 ------- .../java/com/yapp/mission/MissionViewModel.kt | 43 ++++--------------- 3 files changed, 8 insertions(+), 52 deletions(-) diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt index 9aa81001..8e5c61d4 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionContract.kt @@ -27,7 +27,6 @@ sealed class MissionContract { data object ClickCard : Action() data object ShowExitDialog : Action() data object HideExitDialog : Action() - data object RetryPostFortune : Action() } sealed class SideEffect : com.yapp.ui.base.SideEffect { diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt index ad178833..65249b8c 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt @@ -143,12 +143,6 @@ fun MissionScreen( MissionSuccessOverlay() } - state.errorMessage?.let { - ErrorDialog(message = it) { - eventDispatcher(MissionContract.Action.RetryPostFortune) - } - } - if (state.missionMode == MissionMode.PREVIEW) { val insets = WindowInsets.navigationBars.asPaddingValues() @@ -405,16 +399,6 @@ fun MissionSuccessOverlay() { } } -@Composable -fun ErrorDialog(message: String, onConfirm: () -> Unit) { - OrbitDialog( - title = stringResource(id = R.string.error), - message = message, - confirmText = stringResource(id = R.string.confirm), - onConfirm = onConfirm, - ) -} - @Composable fun MissionLoadingScreen() { Box( diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt index 9d8808e4..f54f58e4 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt @@ -9,14 +9,11 @@ import com.yapp.analytics.AnalyticsHelper import com.yapp.domain.MissionMode import com.yapp.domain.model.MissionType import com.yapp.domain.repository.FortuneRepository -import com.yapp.domain.repository.UserInfoRepository import com.yapp.media.haptic.HapticFeedbackManager import com.yapp.media.haptic.HapticType import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.first import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent @@ -30,7 +27,6 @@ class MissionViewModel @Inject constructor( private val analyticsHelper: AnalyticsHelper, private val hapticFeedbackManager: HapticFeedbackManager, private val fortuneRepository: FortuneRepository, - private val userInfoRepository: UserInfoRepository, private val app: Application, private val savedStateHandle: SavedStateHandle, ) : ViewModel(), ContainerHost { @@ -53,7 +49,6 @@ class MissionViewModel @Inject constructor( is MissionContract.Action.ClickCard -> handleMissionProgress(MissionType.TAP) is MissionContract.Action.ShowExitDialog -> showExitDialog() is MissionContract.Action.HideExitDialog -> hideExitDialog() - is MissionContract.Action.RetryPostFortune -> retryPostFortune() } } @@ -138,37 +133,19 @@ class MissionViewModel @Inject constructor( performHapticSuccess() logMissionSuccess(type) if (state.missionMode == MissionMode.REAL) { - postFortune() - } else { - postSideEffect(MissionContract.SideEffect.NavigateBack) - } - } - - private fun postFortune(isRetry: Boolean = false) = intent { - val userId = userInfoRepository.userIdFlow.firstOrNull() ?: return@intent - - val result = withContext(Dispatchers.IO) { - fortuneRepository.postFortune(userId) - } + val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() + val isFortuneCreating = fortuneRepository.isFortuneCreatingFlow.first() - result.onSuccess { data -> - fortuneRepository.saveFortuneId(data.id) - fortuneRepository.saveFortuneScore(data.avgFortuneScore) - - postSideEffect(MissionContract.SideEffect.NavigateToFortune) - }.onFailure { error -> - if (isRetry) { - navigateToHome() + if (hasUnseenFortune || isFortuneCreating) { + postSideEffect(MissionContract.SideEffect.NavigateToFortune) } else { - reduce { state.copy(errorMessage = error.message) } + postSideEffect(MissionContract.SideEffect.NavigateBack) } + } else { + postSideEffect(MissionContract.SideEffect.NavigateBack) } } - fun retryPostFortune() { - postFortune(isRetry = true) - } - private fun logMissionSuccess(type: String) { analyticsHelper.logEvent( AnalyticsEvent( @@ -183,8 +160,4 @@ class MissionViewModel @Inject constructor( private fun performHapticSuccess() { hapticFeedbackManager.performHapticFeedback(HapticType.SUCCESS) } - - private fun navigateToHome() = intent { - postSideEffect(MissionContract.SideEffect.NavigateToHome) - } } From 955c93cb7645bf2c75c1aea7bbc67a931225ddff Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:31:24 +0900 Subject: [PATCH 09/28] =?UTF-8?q?[REFACTOR/#246]=20=EC=9A=B4=EC=84=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20Flow=EB=A1=9C=20?= =?UTF-8?q?=EA=B4=80=EC=B0=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/fortune/FortuneViewModel.kt | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt index 034c1590..b025f409 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt @@ -9,6 +9,7 @@ import com.yapp.fortune.page.toFortunePages import com.yapp.media.decoder.ImageUtils import com.yapp.media.storage.ImageSaver import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost @@ -20,6 +21,13 @@ import java.time.LocalDate import java.time.format.DateTimeFormatter import javax.inject.Inject +private data class FortuneSnapshot( + val fortuneId: Long?, + val fortuneDate: String?, + val isFirstAlarmDismissedToday: Boolean, + val isCreating: Boolean, +) + @HiltViewModel class FortuneViewModel @Inject constructor( private val application: Application, @@ -30,7 +38,7 @@ class FortuneViewModel @Inject constructor( override val container: Container = container( initialState = FortuneContract.State(), ) { - loadFortune() + observeFortune() } fun processAction(action: FortuneContract.Action) { @@ -50,14 +58,34 @@ class FortuneViewModel @Inject constructor( } } - private fun loadFortune() = intent { - val fortuneId = fortuneRepository.fortuneIdFlow.firstOrNull() - val firstDismissedAlarmId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull() - val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull() - fortuneId?.let { fetchAndUpdateFortune(it, firstDismissedAlarmId, fortuneDate) } + private fun observeFortune() = intent { + combine( + fortuneRepository.fortuneIdFlow, + fortuneRepository.fortuneDateFlow, + fortuneRepository.isFirstAlarmDismissedTodayFlow, + fortuneRepository.isFortuneCreatingFlow, + ) { fortuneId: Long?, fortuneDate: String?, isFirstAlarmDismissedToday: Boolean, isCreating: Boolean -> + FortuneSnapshot(fortuneId, fortuneDate, isFirstAlarmDismissedToday, isCreating) + }.collect { (fortuneId, fortuneDate, isFirstAlarmDismissedToday, isCreating) -> + when { + isCreating -> { + reduce { state.copy(isLoading = true) } + } + fortuneId != null -> { + fetchAndUpdateFortune(fortuneId, isFirstAlarmDismissedToday, fortuneDate) + } + else -> { + reduce { state.copy(isLoading = false) } + } + } + } } - private fun fetchAndUpdateFortune(fortuneId: Long, firstDismissedAlarmId: Long?, fortuneDate: String?) = intent { + private fun fetchAndUpdateFortune( + fortuneId: Long, + isFirstAlarmDismissedToday: Boolean, + fortuneDate: String?, + ) = intent { reduce { state.copy(isLoading = true) } fortuneRepository.getFortune(fortuneId).onSuccess { fortune -> @@ -66,7 +94,11 @@ class FortuneViewModel @Inject constructor( val formattedTitle = fortune.dailyFortuneTitle.replace(",", ",\n").trim() val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - val hasReward = (fortuneDate == todayDate) && (firstDismissedAlarmId != null) + + val hasReward = (fortuneDate == todayDate) && isFirstAlarmDismissedToday + + fortuneRepository.markFortuneSeen() + reduce { state.copy( isLoading = false, From 1957cbc32b4306b0ed116d177b722f0421c9f704 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 15:32:12 +0900 Subject: [PATCH 10/28] =?UTF-8?q?[REFACTOR/#246]=20`hasNewFortuneFlow`?= =?UTF-8?q?=EB=A5=BC=20`shouldShowFortuneToolTipFlow`=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/home/src/main/java/com/yapp/home/HomeViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt index ae347cea..1834e476 100644 --- a/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/yapp/home/HomeViewModel.kt @@ -338,7 +338,7 @@ class HomeViewModel @Inject constructor( if (fortuneDate != todayDate) { processAction(HomeContract.Action.ShowNoDailyFortuneDialog) } else { - fortuneRepository.markFortuneAsChecked() + fortuneRepository.markFortuneTooltipShown() postSideEffect(HomeContract.SideEffect.NavigateToFortune) } } @@ -349,12 +349,12 @@ class HomeViewModel @Inject constructor( combine( fortuneRepository.fortuneDateFlow, fortuneRepository.fortuneScoreFlow, - fortuneRepository.hasNewFortuneFlow, - ) { fortuneDate, fortuneScore, hasNewFortune -> + fortuneRepository.shouldShowFortuneToolTipFlow, + ) { fortuneDate, fortuneScore, shouldShowTooltip -> val isTodayFortuneAvailable = fortuneDate == todayDate val finalFortuneScore = if (isTodayFortuneAvailable) fortuneScore ?: -1 else -1 - Pair(finalFortuneScore, hasNewFortune) + Pair(finalFortuneScore, shouldShowTooltip) }.collect { (finalFortuneScore, hasNewFortune) -> reduce { state.copy( From ac3376dedfbdfb5cb72f5c73d7fdbeae3eb76575 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 20:03:01 +0900 Subject: [PATCH 11/28] =?UTF-8?q?[FEAT/#246]=20=EC=9A=B4=EC=84=B8=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=95=88=ED=96=88=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=9A=B4?= =?UTF-8?q?=EC=84=B8=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 4 +-- .../AlarmInteractionActivityReceiver.kt | 25 +++++++++++++++---- .../java/com/yapp/fortune/FortuneNavGraph.kt | 7 +++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 95ede913..fe19284e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,9 +46,7 @@ - + diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt index 009e52c5..f3bd0030 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt @@ -11,6 +11,7 @@ import com.yapp.domain.repository.FortuneRepository import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -39,17 +40,31 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) missionCount != -1 ) - if (!hasValidMissionData) return - CoroutineScope(Dispatchers.IO).launch { - context?.let { + if (!hasValidMissionData) { + val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() + + 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) + } + } + return@launch + } + + context?.let { ctx -> val uriString = "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount" val missionIntent = Intent(Intent.ACTION_VIEW, uriString.toUri()).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - setPackage(it.packageName) + setPackage(ctx.packageName) } - it.startActivity(missionIntent) + ctx.startActivity(missionIntent) } } } diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt index 5f1b5133..0a7b62ce 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.navDeepLink import androidx.navigation.navOptions import androidx.navigation.navigation import com.yapp.common.navigation.OrbitNavigator @@ -19,7 +20,11 @@ fun NavGraphBuilder.fortuneNavGraph( snackBarHostState: SnackbarHostState, ) { navigation(startDestination = FortuneDestination.Fortune) { - composable { backStackEntry -> + composable( + deepLinks = listOf( + navDeepLink { uriPattern = "orbitapp://fortune" }, + ), + ) { backStackEntry -> val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) val coroutineScope = rememberCoroutineScope() From ed0295815a7a2a92276a3fbd4fd9de0b32f5c0db Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 20:07:26 +0900 Subject: [PATCH 12/28] =?UTF-8?q?[FIX/#246]=20=EC=98=A4=EB=8A=98=20?= =?UTF-8?q?=EC=9A=B4=EC=84=B8=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=9A=94=EC=B2=AD=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/datastore/UserPreferences.kt | 30 +++++++++++++++++-- .../datasource/FortuneLocalDataSource.kt | 1 + .../datasource/FortuneLocalDataSourceImpl.kt | 4 +++ .../repositoryimpl/FortuneRepositoryImpl.kt | 1 + .../domain/repository/FortuneRepository.kt | 1 + .../yapp/fortune/worker/PostFortuneWorker.kt | 13 ++++---- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index 3a338739..7ccb25d9 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -77,6 +77,13 @@ class UserPreferences @Inject constructor( .map { it[Keys.FORTUNE_SCORE] } .distinctUntilChanged() + val hasTodayFortuneFlow: Flow = dataStore.data + .catch { emit(emptyPreferences()) } + .map { pref -> + pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null + } + .distinctUntilChanged() + val hasUnseenFortuneFlow: Flow = dataStore.data .map { pref -> pref[Keys.FORTUNE_DATE] == today() && @@ -129,6 +136,20 @@ class UserPreferences @Inject constructor( } } + suspend fun tryMarkFortuneCreating(): Boolean { + var canStart = false + dataStore.edit { pref -> + val hasTodayFortune = pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null + val creating = pref[Keys.FORTUNE_CREATING] ?: false + if (!hasTodayFortune && !creating) { + pref[Keys.FORTUNE_CREATING] = true + pref[Keys.FORTUNE_FAILED] = false + canStart = true + } + } + return canStart + } + suspend fun markFortuneCreating() { dataStore.edit { pref -> pref[Keys.FORTUNE_CREATING] = true @@ -138,12 +159,17 @@ class UserPreferences @Inject constructor( suspend fun markFortuneCreated(fortuneId: Long) { dataStore.edit { pref -> + val isNewForToday = pref[Keys.FORTUNE_ID] != fortuneId || pref[Keys.FORTUNE_DATE] != today() + pref[Keys.FORTUNE_ID] = fortuneId pref[Keys.FORTUNE_DATE] = today() pref[Keys.FORTUNE_CREATING] = false pref[Keys.FORTUNE_FAILED] = false - pref[Keys.FORTUNE_SEEN] = false - pref[Keys.FORTUNE_TOOLTIP_SHOWN] = false + + if (isNewForToday) { + pref[Keys.FORTUNE_SEEN] = false + pref[Keys.FORTUNE_TOOLTIP_SHOWN] = false + } } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt index 5a276483..d5cb50ef 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt @@ -13,6 +13,7 @@ interface FortuneLocalDataSource { val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow + suspend fun tryMarkFortuneCreating(): Boolean suspend fun markFortuneCreating() suspend fun markFortuneCreated(fortuneId: Long) suspend fun markFortuneFailed() diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt index 31f17f7a..0bc58226 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt @@ -17,6 +17,10 @@ class FortuneLocalDataSourceImpl @Inject constructor( override val isFortuneCreatingFlow = userPreferences.isFortuneCreatingFlow override val isFortuneFailedFlow = userPreferences.isFortuneFailedFlow + override suspend fun tryMarkFortuneCreating(): Boolean { + return userPreferences.tryMarkFortuneCreating() + } + override suspend fun markFortuneCreating() { userPreferences.markFortuneCreating() } diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt index 444e9eae..ff566a88 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt @@ -23,6 +23,7 @@ class FortuneRepositoryImpl @Inject constructor( override val isFortuneCreatingFlow: Flow = fortuneLocalDataSource.isFortuneCreatingFlow override val isFortuneFailedFlow: Flow = fortuneLocalDataSource.isFortuneFailedFlow + override suspend fun tryMarkFortuneCreating() = fortuneLocalDataSource.tryMarkFortuneCreating() override suspend fun markFortuneAsCreating() = fortuneLocalDataSource.markFortuneCreating() override suspend fun markFortuneAsCreated(fortuneId: Long) = fortuneLocalDataSource.markFortuneCreated(fortuneId) override suspend fun markFortuneAsFailed() = fortuneLocalDataSource.markFortuneFailed() diff --git a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt index a6d346b8..8c1cb1f1 100644 --- a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt @@ -14,6 +14,7 @@ interface FortuneRepository { val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow + suspend fun tryMarkFortuneCreating(): Boolean suspend fun markFortuneAsCreating() suspend fun markFortuneAsCreated(fortuneId: Long) suspend fun markFortuneAsFailed() diff --git a/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt index a3b3e1ca..751ae536 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt @@ -21,13 +21,16 @@ class PostFortuneWorker @AssistedInject constructor( override suspend fun doWork(): Result { val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() - val creating = fortuneRepository.isFortuneCreatingFlow.first() + if (hasUnseenFortune) return Result.success() - if (hasUnseenFortune || creating) { - return Result.success() - } + val acquired = fortuneRepository.tryMarkFortuneCreating() + if (!acquired) return Result.success() - val userId = userInfoRepository.userIdFlow.firstOrNull() ?: return Result.failure() + val userId = userInfoRepository.userIdFlow.firstOrNull() + ?: run { + fortuneRepository.markFortuneAsFailed() + return Result.failure() + } return try { fortuneRepository.markFortuneAsCreating() From b27784936c7b860c835a0260ecad5ae627065b3f Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 21:42:19 +0900 Subject: [PATCH 13/28] =?UTF-8?q?[FEAT/#246]=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20=EC=8B=9C=20isFirstToday=20=ED=94=8C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=EB=A5=BC=20Fortune=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=84=EB=8B=AC=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B3=B4=EC=83=81=20=EC=97=AC=EB=B6=80=20=ED=8C=90=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/alarm/AlarmConstants.kt | 1 + .../AlarmInteractionActivityReceiver.kt | 4 +- .../com/yapp/alarm/receivers/AlarmReceiver.kt | 51 +++++++++---------- .../com/yapp/datastore/UserPreferences.kt | 29 ----------- .../datasource/FortuneLocalDataSource.kt | 4 -- .../datasource/FortuneLocalDataSourceImpl.kt | 9 ---- .../repositoryimpl/FortuneRepositoryImpl.kt | 3 -- .../domain/repository/FortuneRepository.kt | 5 -- .../java/com/yapp/fortune/FortuneNavGraph.kt | 2 +- .../java/com/yapp/fortune/FortuneViewModel.kt | 29 ++++------- .../com/yapp/fortune/page/FortunePager.kt | 2 + 11 files changed, 40 insertions(+), 99 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt index 3c2541bc..6c174d81 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt @@ -9,6 +9,7 @@ object AlarmConstants { const val EXTRA_NOTIFICATION_ID = "com.yapp.orbit.EXTRA_NOTIFICATION_ID" const val EXTRA_MISSION_TYPE = "com.yapp.orbit.EXTRA_MISSION_TYPE" const val EXTRA_MISSION_COUNT = "com.yapp.orbit.EXTRA_MISSION_COUNT" + const val EXTRA_IS_FIRST_TODAY = "com.yapp.orbit.EXTRA_IS_FIRST_TODAY" const val EXTRA_ALARM = "com.yapp.orbit.EXTRA_ALARM" const val EXTRA_ALARM_DAY = "com.yapp.orbit.EXTRA_ALARM_DAY" diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt index f3bd0030..00fcf885 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt @@ -43,10 +43,10 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) CoroutineScope(Dispatchers.IO).launch { if (!hasValidMissionData) { val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() - if (hasUnseenFortune) { + val isFirstToday = intent.getBooleanExtra(AlarmConstants.EXTRA_IS_FIRST_TODAY, false) context?.let { ctx -> - val uri = "orbitapp://fortune".toUri() + val uri = "orbitapp://fortune?hasReward=$isFirstToday".toUri() val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) setPackage(ctx.packageName) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index e1e3e562..545b5fea 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -105,38 +105,27 @@ class AlarmReceiver : BroadcastReceiver() { androidAlarmScheduler.cancelSnoozedAlarm(notificationId) context.stopService(alarmServiceIntent) - sendBroadCastToCloseAlarmInteractionActivity( - context = context, - notificationId = notificationId, - missionType = missionType, - missionCount = missionCount, - ) - CoroutineScope(Dispatchers.IO).launch { val alarms = alarmUseCase.getAllAlarms().first() val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET - if (!isSnoozeId) { - val today = LocalDate.now().dayOfWeek - fun Alarm.ringsToday(): Boolean { - if (repeatDays == 0) return true - return (repeatDays and (1 shl today.ordinal)) != 0 - } - - val earliestIdToday: Long? = alarms - .asSequence() - .filter { it.isAlarmActive && it.ringsToday() } - .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second })) - .firstOrNull() - ?.id - - val isEarliestAlarmDismissedToday = (earliestIdToday == notificationId) - - if (isEarliestAlarmDismissedToday) { - fortuneRepository.saveFirstAlarmDismissedToday(notificationId) - } + + val today = LocalDate.now().dayOfWeek + fun Alarm.ringsToday(): Boolean { + if (repeatDays == 0) return true + return (repeatDays and (1 shl today.ordinal)) != 0 } + val earliestIdToday: Long? = alarms + .asSequence() + .filter { it.isAlarmActive && it.ringsToday() } + .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second })) + .firstOrNull() + ?.id + + val isEarliestAlarmDismissedToday = + !isSnoozeId && (earliestIdToday == notificationId) + val isFirstAlarm = alarms.firstOrNull()?.id == notificationId analyticsHelper.logEvent( AnalyticsEvent( @@ -147,6 +136,14 @@ class AlarmReceiver : BroadcastReceiver() { ), ), ) + + sendBroadCastToCloseAlarmInteractionActivity( + context = context, + notificationId = notificationId, + missionType = missionType, + missionCount = missionCount, + isEarliestToday = isEarliestAlarmDismissedToday, + ) } Toast.makeText(context, "알람이 해제되었어요", Toast.LENGTH_SHORT).show() @@ -193,12 +190,14 @@ class AlarmReceiver : BroadcastReceiver() { notificationId: Long, missionType: Int, missionCount: Int, + isEarliestToday: Boolean, ) { val intent = Intent(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE).apply { putExtra(AlarmConstants.EXTRA_IS_SNOOZED, false) putExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, notificationId) putExtra(AlarmConstants.EXTRA_MISSION_TYPE, missionType) putExtra(AlarmConstants.EXTRA_MISSION_COUNT, missionCount) + putExtra(AlarmConstants.EXTRA_IS_FIRST_TODAY, isEarliestToday) } context.sendBroadcast(intent) } diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index 7ccb25d9..bc165d88 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -1,6 +1,5 @@ package com.yapp.datastore -import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -35,9 +34,6 @@ class UserPreferences @Inject constructor( val FORTUNE_TOOLTIP_SHOWN = booleanPreferencesKey("fortune_tooltip_shown") val FORTUNE_CREATING = booleanPreferencesKey("fortune_creating") val FORTUNE_FAILED = booleanPreferencesKey("fortune_failed") - - val FIRST_DISMISSED_ALARM_ID = longPreferencesKey("first_dismissed_alarm_id") - val DISMISSED_DATE = stringPreferencesKey("dismissed_date") } private fun today(): String = LocalDate.now().format(DateTimeFormatter.ISO_DATE) @@ -111,13 +107,6 @@ class UserPreferences @Inject constructor( .map { it[Keys.FORTUNE_FAILED] ?: false } .distinctUntilChanged() - val isFirstAlarmDismissedTodayFlow: Flow = dataStore.data - .catch { emit(emptyPreferences()) } - .map { pref -> - pref[Keys.DISMISSED_DATE] == today() && pref[Keys.FIRST_DISMISSED_ALARM_ID] != null - } - .distinctUntilChanged() - suspend fun saveUserId(userId: Long) { dataStore.edit { pref -> pref[Keys.USER_ID] = userId @@ -204,24 +193,6 @@ class UserPreferences @Inject constructor( } } - suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) { - dataStore.edit { pref -> - if (pref[Keys.DISMISSED_DATE] == today() && pref[Keys.FIRST_DISMISSED_ALARM_ID] != null) { - Log.d("UserPreferences", "이미 오늘 첫 알람 해제 기록이 있음") - return@edit - } - pref[Keys.FIRST_DISMISSED_ALARM_ID] = firstAlarmId - pref[Keys.DISMISSED_DATE] = today() - } - } - - suspend fun clearFirstAlarmDismissedToday() { - dataStore.edit { pref -> - pref.remove(Keys.FIRST_DISMISSED_ALARM_ID) - pref.remove(Keys.DISMISSED_DATE) - } - } - suspend fun clearUserData() { dataStore.edit { pref -> pref.clear() } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt index d5cb50ef..b08c8dcd 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt @@ -9,7 +9,6 @@ interface FortuneLocalDataSource { val fortuneScoreFlow: Flow val hasUnseenFortuneFlow: Flow val shouldShowFortuneToolTipFlow: Flow - val isFirstAlarmDismissedTodayFlow: Flow val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow @@ -22,8 +21,5 @@ interface FortuneLocalDataSource { suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) - suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) - suspend fun clearFirstAlarmDismissedToday() - suspend fun clearFortuneData() } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt index 0bc58226..71932301 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt @@ -13,7 +13,6 @@ class FortuneLocalDataSourceImpl @Inject constructor( override val fortuneScoreFlow = userPreferences.fortuneScoreFlow override val hasUnseenFortuneFlow = userPreferences.hasUnseenFortuneFlow override val shouldShowFortuneToolTipFlow = userPreferences.shouldShowFortuneToolTipFlow - override val isFirstAlarmDismissedTodayFlow = userPreferences.isFirstAlarmDismissedTodayFlow override val isFortuneCreatingFlow = userPreferences.isFortuneCreatingFlow override val isFortuneFailedFlow = userPreferences.isFortuneFailedFlow @@ -49,14 +48,6 @@ class FortuneLocalDataSourceImpl @Inject constructor( userPreferences.saveFortuneScore(score) } - override suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) { - userPreferences.saveFirstAlarmDismissedToday(firstAlarmId) - } - - override suspend fun clearFirstAlarmDismissedToday() { - userPreferences.clearFirstAlarmDismissedToday() - } - override suspend fun clearFortuneData() { userPreferences.clearFortuneData() } diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt index ff566a88..af1086e1 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt @@ -19,7 +19,6 @@ class FortuneRepositoryImpl @Inject constructor( override val fortuneScoreFlow: Flow = fortuneLocalDataSource.fortuneScoreFlow override val hasUnseenFortuneFlow: Flow = fortuneLocalDataSource.hasUnseenFortuneFlow override val shouldShowFortuneToolTipFlow: Flow = fortuneLocalDataSource.shouldShowFortuneToolTipFlow - override val isFirstAlarmDismissedTodayFlow: Flow = fortuneLocalDataSource.isFirstAlarmDismissedTodayFlow override val isFortuneCreatingFlow: Flow = fortuneLocalDataSource.isFortuneCreatingFlow override val isFortuneFailedFlow: Flow = fortuneLocalDataSource.isFortuneFailedFlow @@ -31,8 +30,6 @@ class FortuneRepositoryImpl @Inject constructor( override suspend fun markFortuneTooltipShown() = fortuneLocalDataSource.markFortuneTooltipShown() override suspend fun saveFortuneImageId(imageResId: Int) = fortuneLocalDataSource.saveFortuneImageId(imageResId) override suspend fun saveFortuneScore(score: Int) = fortuneLocalDataSource.saveFortuneScore(score) - override suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) = fortuneLocalDataSource.saveFirstAlarmDismissedToday(firstAlarmId) - override suspend fun clearFirstAlarmDismissedToday() = fortuneLocalDataSource.clearFirstAlarmDismissedToday() override suspend fun clearFortuneData() = fortuneLocalDataSource.clearFortuneData() override suspend fun postFortune(userId: Long): Result { diff --git a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt index 8c1cb1f1..cc9f6a78 100644 --- a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt @@ -10,7 +10,6 @@ interface FortuneRepository { val fortuneScoreFlow: Flow val hasUnseenFortuneFlow: Flow val shouldShowFortuneToolTipFlow: Flow - val isFirstAlarmDismissedTodayFlow: Flow val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow @@ -22,10 +21,6 @@ interface FortuneRepository { suspend fun markFortuneTooltipShown() suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) - - suspend fun saveFirstAlarmDismissedToday(firstAlarmId: Long) - suspend fun clearFirstAlarmDismissedToday() - suspend fun clearFortuneData() suspend fun postFortune(userId: Long): Result diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt index 0a7b62ce..fb297e57 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt @@ -22,7 +22,7 @@ fun NavGraphBuilder.fortuneNavGraph( navigation(startDestination = FortuneDestination.Fortune) { composable( deepLinks = listOf( - navDeepLink { uriPattern = "orbitapp://fortune" }, + navDeepLink { uriPattern = "orbitapp://fortune?hasReward={hasReward}" }, ), ) { backStackEntry -> val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt index b025f409..a9ecaa46 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt @@ -3,6 +3,7 @@ package com.yapp.fortune import android.app.Application import android.util.Log import androidx.annotation.DrawableRes +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.yapp.domain.repository.FortuneRepository import com.yapp.fortune.page.toFortunePages @@ -17,24 +18,18 @@ import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container -import java.time.LocalDate -import java.time.format.DateTimeFormatter import javax.inject.Inject -private data class FortuneSnapshot( - val fortuneId: Long?, - val fortuneDate: String?, - val isFirstAlarmDismissedToday: Boolean, - val isCreating: Boolean, -) - @HiltViewModel class FortuneViewModel @Inject constructor( private val application: Application, private val fortuneRepository: FortuneRepository, private val imageSaver: ImageSaver, + savedStateHandle: SavedStateHandle, ) : ViewModel(), ContainerHost { + private val hasReward = savedStateHandle.get("hasReward")?.toBooleanStrictOrNull() ?: false + override val container: Container = container( initialState = FortuneContract.State(), ) { @@ -61,18 +56,16 @@ class FortuneViewModel @Inject constructor( private fun observeFortune() = intent { combine( fortuneRepository.fortuneIdFlow, - fortuneRepository.fortuneDateFlow, - fortuneRepository.isFirstAlarmDismissedTodayFlow, fortuneRepository.isFortuneCreatingFlow, - ) { fortuneId: Long?, fortuneDate: String?, isFirstAlarmDismissedToday: Boolean, isCreating: Boolean -> - FortuneSnapshot(fortuneId, fortuneDate, isFirstAlarmDismissedToday, isCreating) - }.collect { (fortuneId, fortuneDate, isFirstAlarmDismissedToday, isCreating) -> + ) { fortuneId: Long?, isCreating: Boolean -> + Pair(fortuneId, isCreating) + }.collect { (fortuneId, isCreating) -> when { isCreating -> { reduce { state.copy(isLoading = true) } } fortuneId != null -> { - fetchAndUpdateFortune(fortuneId, isFirstAlarmDismissedToday, fortuneDate) + fetchAndUpdateFortune(fortuneId, hasReward == true) } else -> { reduce { state.copy(isLoading = false) } @@ -83,8 +76,7 @@ class FortuneViewModel @Inject constructor( private fun fetchAndUpdateFortune( fortuneId: Long, - isFirstAlarmDismissedToday: Boolean, - fortuneDate: String?, + hasReward: Boolean, ) = intent { reduce { state.copy(isLoading = true) } @@ -93,9 +85,6 @@ class FortuneViewModel @Inject constructor( val imageId = savedImageId ?: getRandomImage() val formattedTitle = fortune.dailyFortuneTitle.replace(",", ",\n").trim() - val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE) - - val hasReward = (fortuneDate == todayDate) && isFirstAlarmDismissedToday fortuneRepository.markFortuneSeen() diff --git a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt index 08b7cfc5..aa1dc78b 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/page/FortunePager.kt @@ -51,11 +51,13 @@ fun FortunePager( val index = (page - 1).coerceIn(0, state.fortunePages.lastIndex) FortunePageLayout(state.fortunePages[index]) } + 5 -> FortuneCompletePage( hasReward = state.hasReward, onCompleteClick = onNextStep, onNavigateToHome = onNavigateToHome, ) + else -> {} } } From 979670c8494d6765c7c3e8ca552bc8c98fdad79b Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 21:45:52 +0900 Subject: [PATCH 14/28] =?UTF-8?q?[REMOVE/#246]=20`hasTodayFortuneFlow`=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/datastore/UserPreferences.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index bc165d88..f38eef9a 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -73,13 +73,6 @@ class UserPreferences @Inject constructor( .map { it[Keys.FORTUNE_SCORE] } .distinctUntilChanged() - val hasTodayFortuneFlow: Flow = dataStore.data - .catch { emit(emptyPreferences()) } - .map { pref -> - pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null - } - .distinctUntilChanged() - val hasUnseenFortuneFlow: Flow = dataStore.data .map { pref -> pref[Keys.FORTUNE_DATE] == today() && From d30bebaecfe2567d36b196b18eb8f395e819ce23 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 23:37:40 +0900 Subject: [PATCH 15/28] =?UTF-8?q?[FIX/#246]=20compileSdk=EB=A5=BC=20androi?= =?UTF-8?q?d=20=EB=B8=94=EB=A1=9D=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3b5e9d8a..6c350ede 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,12 +8,12 @@ plugins { android { namespace = "com.yapp.orbit" + compileSdk = 35 defaultConfig { versionCode = 6 versionName = "1.1.3" targetSdk = 35 - compileSdk = 35 } buildTypes { From 9ba43edede071f0fc5911e159d91b1597ca65e04 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 20 Aug 2025 23:44:30 +0900 Subject: [PATCH 16/28] =?UTF-8?q?[CHORE/#246]=20WorkManager=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe19284e..6eabb63b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,7 +82,11 @@ + android:exported="false" + tools:node="merge"> + From 8f482e5ec45be6bee4249550bd87ce6911115bbe Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:19:09 +0900 Subject: [PATCH 17/28] =?UTF-8?q?[REFACTOR/#246]=20`AlarmInteractionActivi?= =?UTF-8?q?tyReceiver`=EC=97=90=EC=84=9C=20`goAsync`=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20=EB=B0=B1=EA=B7=B8=EB=9D=BC?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EC=9E=91=EC=97=85=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AlarmInteractionActivityReceiver.kt | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt index 00fcf885..3b6f8e84 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @AndroidEntryPoint @@ -40,31 +41,42 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) missionCount != -1 ) - CoroutineScope(Dispatchers.IO).launch { - if (!hasValidMissionData) { - val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() - if (hasUnseenFortune) { - val isFirstToday = intent.getBooleanExtra(AlarmConstants.EXTRA_IS_FIRST_TODAY, false) - context?.let { ctx -> - val uri = "orbitapp://fortune?hasReward=$isFirstToday".toUri() - val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - setPackage(ctx.packageName) + val pending = goAsync() + CoroutineScope(Dispatchers.Main).launch { + try { + if (!hasValidMissionData) { + val hasUnseenFortune = withContext(Dispatchers.IO) { + fortuneRepository.hasUnseenFortuneFlow.first() + } + if (hasUnseenFortune) { + val isFirstToday = intent.getBooleanExtra( + AlarmConstants.EXTRA_IS_FIRST_TODAY, + false, + ) + context?.let { ctx -> + val uri = "orbitapp://fortune?hasReward=$isFirstToday".toUri() + val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage(ctx.packageName) + } + ctx.startActivity(fortuneIntent) } - ctx.startActivity(fortuneIntent) } + return@launch } - return@launch - } - context?.let { ctx -> - val uriString = - "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount" - val missionIntent = Intent(Intent.ACTION_VIEW, uriString.toUri()).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - setPackage(ctx.packageName) + context?.let { ctx -> + val uriString = + "orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount" + val missionIntent = + Intent(Intent.ACTION_VIEW, uriString.toUri()).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setPackage(ctx.packageName) + } + ctx.startActivity(missionIntent) } - ctx.startActivity(missionIntent) + } finally { + pending.finish() } } } From 486302d714042d9def644de6eb4e43103f61fc7e Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:20:24 +0900 Subject: [PATCH 18/28] =?UTF-8?q?[CHORE/#246]=20WorkManager=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/fortune/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/fortune/build.gradle.kts b/feature/fortune/build.gradle.kts index 533e41d5..543510e8 100644 --- a/feature/fortune/build.gradle.kts +++ b/feature/fortune/build.gradle.kts @@ -18,7 +18,8 @@ dependencies { implementation(libs.orbit.viewmodel) implementation(libs.coil.compose) implementation(libs.androidx.work.runtime) - implementation(libs.androidx.work.testing) + testImplementation(libs.androidx.work.testing) + androidTestImplementation(libs.androidx.work.testing) implementation(projects.domain) implementation(projects.core.media) } From e155d6063095131e899f508a27af8191f31a4023 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:27:03 +0900 Subject: [PATCH 19/28] =?UTF-8?q?[FIX/#246]=20`Alarm.ringsToday`=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/alarm/receivers/AlarmReceiver.kt | 6 ++++-- .../src/main/java/com/yapp/domain/model/AlarmDay.kt | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index 545b5fea..c19bccad 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -12,6 +12,7 @@ import com.yapp.alarm.services.AlarmService import com.yapp.analytics.AnalyticsEvent import com.yapp.analytics.AnalyticsHelper import com.yapp.domain.model.Alarm +import com.yapp.domain.model.toAlarmDay import com.yapp.domain.model.toTimeString import com.yapp.domain.repository.FortuneRepository import com.yapp.domain.usecase.AlarmUseCase @@ -110,10 +111,11 @@ class AlarmReceiver : BroadcastReceiver() { val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET - val today = LocalDate.now().dayOfWeek fun Alarm.ringsToday(): Boolean { if (repeatDays == 0) return true - return (repeatDays and (1 shl today.ordinal)) != 0 + + val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay() + return (repeatDays and todayAlarmDay.bitValue) != 0 } val earliestIdToday: Long? = alarms diff --git a/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt b/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt index beaead69..7f349ed5 100644 --- a/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt +++ b/domain/src/main/java/com/yapp/domain/model/AlarmDay.kt @@ -1,5 +1,7 @@ package com.yapp.domain.model +import java.time.DayOfWeek + enum class AlarmDay(val bitValue: Int) { SUN(0b0000001), // 1 MON(0b0000010), // 2 @@ -11,8 +13,13 @@ enum class AlarmDay(val bitValue: Int) { ; } -fun AlarmDay.toDayOfWeek(): java.time.DayOfWeek { - return java.time.DayOfWeek.of(((this.ordinal + 6) % 7) + 1) +fun AlarmDay.toDayOfWeek(): DayOfWeek { + return DayOfWeek.of(((this.ordinal + 6) % 7) + 1) +} + +fun DayOfWeek.toAlarmDay(): AlarmDay { + val index = (this.value % 7) + return AlarmDay.entries[index] } fun Set.toRepeatDays(): Int { From 53dff08bb858eb98985fdbe3009067be15d0dcf2 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:29:53 +0900 Subject: [PATCH 20/28] =?UTF-8?q?[FIX/#246]=20DataStore=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=9D=BD=EC=96=B4?= =?UTF-8?q?=EC=98=A4=EC=A7=80=20=EB=AA=BB=ED=95=A0=20=EB=95=8C=20emptyPref?= =?UTF-8?q?erences()=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/datastore/UserPreferences.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index f38eef9a..5ec30418 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -74,6 +74,7 @@ class UserPreferences @Inject constructor( .distinctUntilChanged() val hasUnseenFortuneFlow: Flow = dataStore.data + .catch { emit(emptyPreferences()) } .map { pref -> pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null && From ff346c1e76b063be7a76cc2efb06822fb149a1cb Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:31:27 +0900 Subject: [PATCH 21/28] =?UTF-8?q?[FIX/#246]=20=EB=8B=A8=EB=B0=9C=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=EC=9D=84=20=ED=95=B4=EC=A0=9C=ED=96=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=EB=8F=84=20=EB=B9=84=EA=B5=90=EA=B5=B0?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index c19bccad..2e1764d0 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -120,7 +120,7 @@ class AlarmReceiver : BroadcastReceiver() { val earliestIdToday: Long? = alarms .asSequence() - .filter { it.isAlarmActive && it.ringsToday() } + .filter { (it.isAlarmActive || it.id == notificationId) && it.ringsToday() } .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second })) .firstOrNull() ?.id From abaa8ea097583fb351578dba97bb77f6efefa7b4 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 00:33:04 +0900 Subject: [PATCH 22/28] =?UTF-8?q?[FIX/#246]=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=EC=9D=B4=20=EC=97=86=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=EC=97=90=EB=8F=84=20shouldShowMissionStart=EA=B0=80=20true?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95=EB=90=98=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/alarm/interaction/action/AlarmActionViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt index bc5d81af..49182b15 100644 --- a/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt +++ b/feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt @@ -57,7 +57,7 @@ class AlarmActionViewModel @Inject constructor( private fun fetchShouldShowMissionStart() = intent { reduce { - state.copy(shouldShowMissionStart = alarm?.missionType != MissionType.NONE) + state.copy(shouldShowMissionStart = (alarm?.missionType ?: MissionType.NONE) != MissionType.NONE) } } From 9cdb8ba2678f68f1d36d3f9c184f897ae5c60fc0 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 01:58:32 +0900 Subject: [PATCH 23/28] =?UTF-8?q?[REFACTOR/#246]=20=EC=B5=9C=EC=B4=88=20?= =?UTF-8?q?=EC=95=8C=EB=9E=8C=20=ED=95=B4=EC=A0=9C=20=EC=8B=9C=20=EC=9A=B4?= =?UTF-8?q?=EC=84=B8=20=ED=9A=8D=EB=93=9D=20=EC=97=AC=EB=B6=80=EB=A5=BC=20?= =?UTF-8?q?`SavedStateHandle`=20=EB=8C=80=EC=8B=A0=20`UserPreferences`?= =?UTF-8?q?=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/alarm/AlarmConstants.kt | 1 - .../AlarmInteractionActivityReceiver.kt | 6 +----- .../com/yapp/alarm/receivers/AlarmReceiver.kt | 5 ++--- .../com/yapp/datastore/UserPreferences.kt | 21 +++++++++++++++++++ .../datasource/FortuneLocalDataSource.kt | 2 ++ .../datasource/FortuneLocalDataSourceImpl.kt | 5 +++++ .../repositoryimpl/FortuneRepositoryImpl.kt | 3 +++ .../domain/repository/FortuneRepository.kt | 3 +++ .../java/com/yapp/fortune/FortuneNavGraph.kt | 2 +- .../java/com/yapp/fortune/FortuneViewModel.kt | 17 +++++++-------- .../java/com/yapp/mission/MissionViewModel.kt | 3 +++ 11 files changed, 48 insertions(+), 20 deletions(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt index 6c174d81..3c2541bc 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt @@ -9,7 +9,6 @@ object AlarmConstants { const val EXTRA_NOTIFICATION_ID = "com.yapp.orbit.EXTRA_NOTIFICATION_ID" const val EXTRA_MISSION_TYPE = "com.yapp.orbit.EXTRA_MISSION_TYPE" const val EXTRA_MISSION_COUNT = "com.yapp.orbit.EXTRA_MISSION_COUNT" - const val EXTRA_IS_FIRST_TODAY = "com.yapp.orbit.EXTRA_IS_FIRST_TODAY" const val EXTRA_ALARM = "com.yapp.orbit.EXTRA_ALARM" const val EXTRA_ALARM_DAY = "com.yapp.orbit.EXTRA_ALARM_DAY" diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt index 3b6f8e84..8d2389b7 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt @@ -49,12 +49,8 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity) fortuneRepository.hasUnseenFortuneFlow.first() } if (hasUnseenFortune) { - val isFirstToday = intent.getBooleanExtra( - AlarmConstants.EXTRA_IS_FIRST_TODAY, - false, - ) context?.let { ctx -> - val uri = "orbitapp://fortune?hasReward=$isFirstToday".toUri() + val uri = "orbitapp://fortune".toUri() val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) setPackage(ctx.packageName) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index 2e1764d0..371beb55 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -128,6 +128,8 @@ class AlarmReceiver : BroadcastReceiver() { val isEarliestAlarmDismissedToday = !isSnoozeId && (earliestIdToday == notificationId) + if (isEarliestAlarmDismissedToday) fortuneRepository.markFirstAlarmDismissedToday() + val isFirstAlarm = alarms.firstOrNull()?.id == notificationId analyticsHelper.logEvent( AnalyticsEvent( @@ -144,7 +146,6 @@ class AlarmReceiver : BroadcastReceiver() { notificationId = notificationId, missionType = missionType, missionCount = missionCount, - isEarliestToday = isEarliestAlarmDismissedToday, ) } @@ -192,14 +193,12 @@ class AlarmReceiver : BroadcastReceiver() { notificationId: Long, missionType: Int, missionCount: Int, - isEarliestToday: Boolean, ) { val intent = Intent(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE).apply { putExtra(AlarmConstants.EXTRA_IS_SNOOZED, false) putExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, notificationId) putExtra(AlarmConstants.EXTRA_MISSION_TYPE, missionType) putExtra(AlarmConstants.EXTRA_MISSION_COUNT, missionCount) - putExtra(AlarmConstants.EXTRA_IS_FIRST_TODAY, isEarliestToday) } context.sendBroadcast(intent) } diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index 5ec30418..cae31397 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -1,5 +1,6 @@ package com.yapp.datastore +import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -34,6 +35,9 @@ class UserPreferences @Inject constructor( val FORTUNE_TOOLTIP_SHOWN = booleanPreferencesKey("fortune_tooltip_shown") val FORTUNE_CREATING = booleanPreferencesKey("fortune_creating") val FORTUNE_FAILED = booleanPreferencesKey("fortune_failed") + + val FIRST_ALARM_DISMISSED_TODAY = booleanPreferencesKey("first_alarm_dismissed_today") + val FIRST_ALARM_DISMISSED_DATE = stringPreferencesKey("first_alarm_dismissed_date") } private fun today(): String = LocalDate.now().format(DateTimeFormatter.ISO_DATE) @@ -101,6 +105,15 @@ class UserPreferences @Inject constructor( .map { it[Keys.FORTUNE_FAILED] ?: false } .distinctUntilChanged() + val isFirstAlarmDismissedTodayFlow: Flow = dataStore.data + .catch { emit(emptyPreferences()) } + .map { pref -> + val flag = pref[Keys.FIRST_ALARM_DISMISSED_TODAY] ?: false + val date = pref[Keys.FIRST_ALARM_DISMISSED_DATE] + flag && date == today() + } + .distinctUntilChanged() + suspend fun saveUserId(userId: Long) { dataStore.edit { pref -> pref[Keys.USER_ID] = userId @@ -143,6 +156,7 @@ class UserPreferences @Inject constructor( suspend fun markFortuneCreated(fortuneId: Long) { dataStore.edit { pref -> val isNewForToday = pref[Keys.FORTUNE_ID] != fortuneId || pref[Keys.FORTUNE_DATE] != today() + Log.d("UserPreferences", "markFortuneCreated: isNewForToday=$isNewForToday, fortuneId=$fortuneId") pref[Keys.FORTUNE_ID] = fortuneId pref[Keys.FORTUNE_DATE] = today() @@ -187,6 +201,13 @@ class UserPreferences @Inject constructor( } } + suspend fun markFirstAlarmDismissedToday() { + dataStore.edit { pref -> + pref[Keys.FIRST_ALARM_DISMISSED_TODAY] = true + pref[Keys.FIRST_ALARM_DISMISSED_DATE] = today() + } + } + suspend fun clearUserData() { dataStore.edit { pref -> pref.clear() } } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt index b08c8dcd..7b574bc0 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSource.kt @@ -11,6 +11,7 @@ interface FortuneLocalDataSource { val shouldShowFortuneToolTipFlow: Flow val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow + val isFirstAlarmDismissedTodayFlow: Flow suspend fun tryMarkFortuneCreating(): Boolean suspend fun markFortuneCreating() @@ -20,6 +21,7 @@ interface FortuneLocalDataSource { suspend fun markFortuneTooltipShown() suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) + suspend fun markFirstAlarmDismissedToday() suspend fun clearFortuneData() } diff --git a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt index 71932301..80712f16 100644 --- a/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt +++ b/data/src/main/java/com/yapp/data/local/datasource/FortuneLocalDataSourceImpl.kt @@ -15,6 +15,7 @@ class FortuneLocalDataSourceImpl @Inject constructor( override val shouldShowFortuneToolTipFlow = userPreferences.shouldShowFortuneToolTipFlow override val isFortuneCreatingFlow = userPreferences.isFortuneCreatingFlow override val isFortuneFailedFlow = userPreferences.isFortuneFailedFlow + override val isFirstAlarmDismissedTodayFlow = userPreferences.isFirstAlarmDismissedTodayFlow override suspend fun tryMarkFortuneCreating(): Boolean { return userPreferences.tryMarkFortuneCreating() @@ -48,6 +49,10 @@ class FortuneLocalDataSourceImpl @Inject constructor( userPreferences.saveFortuneScore(score) } + override suspend fun markFirstAlarmDismissedToday() { + userPreferences.markFirstAlarmDismissedToday() + } + override suspend fun clearFortuneData() { userPreferences.clearFortuneData() } diff --git a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt index af1086e1..23271c5f 100644 --- a/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt +++ b/data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt @@ -21,6 +21,7 @@ class FortuneRepositoryImpl @Inject constructor( override val shouldShowFortuneToolTipFlow: Flow = fortuneLocalDataSource.shouldShowFortuneToolTipFlow override val isFortuneCreatingFlow: Flow = fortuneLocalDataSource.isFortuneCreatingFlow override val isFortuneFailedFlow: Flow = fortuneLocalDataSource.isFortuneFailedFlow + override val isFirstAlarmDismissedTodayFlow: Flow = fortuneLocalDataSource.isFirstAlarmDismissedTodayFlow override suspend fun tryMarkFortuneCreating() = fortuneLocalDataSource.tryMarkFortuneCreating() override suspend fun markFortuneAsCreating() = fortuneLocalDataSource.markFortuneCreating() @@ -30,6 +31,8 @@ class FortuneRepositoryImpl @Inject constructor( override suspend fun markFortuneTooltipShown() = fortuneLocalDataSource.markFortuneTooltipShown() override suspend fun saveFortuneImageId(imageResId: Int) = fortuneLocalDataSource.saveFortuneImageId(imageResId) override suspend fun saveFortuneScore(score: Int) = fortuneLocalDataSource.saveFortuneScore(score) + override suspend fun markFirstAlarmDismissedToday() = fortuneLocalDataSource.markFirstAlarmDismissedToday() + override suspend fun clearFortuneData() = fortuneLocalDataSource.clearFortuneData() override suspend fun postFortune(userId: Long): Result { diff --git a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt index cc9f6a78..89cc4a67 100644 --- a/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt +++ b/domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt @@ -12,6 +12,7 @@ interface FortuneRepository { val shouldShowFortuneToolTipFlow: Flow val isFortuneCreatingFlow: Flow val isFortuneFailedFlow: Flow + val isFirstAlarmDismissedTodayFlow: Flow suspend fun tryMarkFortuneCreating(): Boolean suspend fun markFortuneAsCreating() @@ -21,6 +22,8 @@ interface FortuneRepository { suspend fun markFortuneTooltipShown() suspend fun saveFortuneImageId(imageResId: Int) suspend fun saveFortuneScore(score: Int) + suspend fun markFirstAlarmDismissedToday() + suspend fun clearFortuneData() suspend fun postFortune(userId: Long): Result diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt index fb297e57..0a7b62ce 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt @@ -22,7 +22,7 @@ fun NavGraphBuilder.fortuneNavGraph( navigation(startDestination = FortuneDestination.Fortune) { composable( deepLinks = listOf( - navDeepLink { uriPattern = "orbitapp://fortune?hasReward={hasReward}" }, + navDeepLink { uriPattern = "orbitapp://fortune" }, ), ) { backStackEntry -> val viewModel = backStackEntry.sharedHiltViewModel(navigator.navController) diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt index a9ecaa46..003aa09d 100644 --- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt +++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt @@ -3,7 +3,6 @@ package com.yapp.fortune import android.app.Application import android.util.Log import androidx.annotation.DrawableRes -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.yapp.domain.repository.FortuneRepository import com.yapp.fortune.page.toFortunePages @@ -25,11 +24,8 @@ class FortuneViewModel @Inject constructor( private val application: Application, private val fortuneRepository: FortuneRepository, private val imageSaver: ImageSaver, - savedStateHandle: SavedStateHandle, ) : ViewModel(), ContainerHost { - private val hasReward = savedStateHandle.get("hasReward")?.toBooleanStrictOrNull() ?: false - override val container: Container = container( initialState = FortuneContract.State(), ) { @@ -56,16 +52,17 @@ class FortuneViewModel @Inject constructor( private fun observeFortune() = intent { combine( fortuneRepository.fortuneIdFlow, + fortuneRepository.isFirstAlarmDismissedTodayFlow, fortuneRepository.isFortuneCreatingFlow, - ) { fortuneId: Long?, isCreating: Boolean -> - Pair(fortuneId, isCreating) - }.collect { (fortuneId, isCreating) -> + ) { fortuneId: Long?, isFirstAlarmDismissedToday: Boolean, isCreating: Boolean -> + Triple(fortuneId, isFirstAlarmDismissedToday, isCreating) + }.collect { (fortuneId, isFirstAlarmDismissedToday, isCreating) -> when { isCreating -> { reduce { state.copy(isLoading = true) } } fortuneId != null -> { - fetchAndUpdateFortune(fortuneId, hasReward == true) + fetchAndUpdateFortune(fortuneId, isFirstAlarmDismissedToday) } else -> { reduce { state.copy(isLoading = false) } @@ -76,7 +73,7 @@ class FortuneViewModel @Inject constructor( private fun fetchAndUpdateFortune( fortuneId: Long, - hasReward: Boolean, + isFirstAlarmDismissedToday: Boolean, ) = intent { reduce { state.copy(isLoading = true) } @@ -96,7 +93,7 @@ class FortuneViewModel @Inject constructor( avgFortuneScore = fortune.avgFortuneScore, fortunePages = fortune.toFortunePages(), fortuneImageId = imageId, - hasReward = hasReward, + hasReward = isFirstAlarmDismissedToday, ) } }.onFailure { error -> diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt index f54f58e4..6ecd241f 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt @@ -1,6 +1,7 @@ package com.yapp.mission import android.app.Application +import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent @@ -136,6 +137,8 @@ class MissionViewModel @Inject constructor( val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() val isFortuneCreating = fortuneRepository.isFortuneCreatingFlow.first() + Log.d("MissionViewModel", "hasUnseenFortune: $hasUnseenFortune, isFortuneCreating: $isFortuneCreating") + if (hasUnseenFortune || isFortuneCreating) { postSideEffect(MissionContract.SideEffect.NavigateToFortune) } else { From 4b6993afdb64970d4d6130aa514ebc03996090d7 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 01:59:55 +0900 Subject: [PATCH 24/28] =?UTF-8?q?[REMOVE/#246]=20UserPreferences,=20Missio?= =?UTF-8?q?nViewModel=20=EB=94=94=EB=B2=84=EA=B7=B8=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/datastore/UserPreferences.kt | 2 -- .../mission/src/main/java/com/yapp/mission/MissionViewModel.kt | 3 --- 2 files changed, 5 deletions(-) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index cae31397..422d0563 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -1,6 +1,5 @@ package com.yapp.datastore -import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey @@ -156,7 +155,6 @@ class UserPreferences @Inject constructor( suspend fun markFortuneCreated(fortuneId: Long) { dataStore.edit { pref -> val isNewForToday = pref[Keys.FORTUNE_ID] != fortuneId || pref[Keys.FORTUNE_DATE] != today() - Log.d("UserPreferences", "markFortuneCreated: isNewForToday=$isNewForToday, fortuneId=$fortuneId") pref[Keys.FORTUNE_ID] = fortuneId pref[Keys.FORTUNE_DATE] = today() diff --git a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt index 6ecd241f..f54f58e4 100644 --- a/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt +++ b/feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt @@ -1,7 +1,6 @@ package com.yapp.mission import android.app.Application -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissIntent @@ -137,8 +136,6 @@ class MissionViewModel @Inject constructor( val hasUnseenFortune = fortuneRepository.hasUnseenFortuneFlow.first() val isFortuneCreating = fortuneRepository.isFortuneCreatingFlow.first() - Log.d("MissionViewModel", "hasUnseenFortune: $hasUnseenFortune, isFortuneCreating: $isFortuneCreating") - if (hasUnseenFortune || isFortuneCreating) { postSideEffect(MissionContract.SideEffect.NavigateToFortune) } else { From 0cdb8905ca5556270af16071faf0dcb7480a7a64 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 02:10:59 +0900 Subject: [PATCH 25/28] =?UTF-8?q?[FIX/#246]=20=EC=98=A4=EB=8A=98=EC=9D=98?= =?UTF-8?q?=20=EC=9A=B4=EC=84=B8=EA=B0=80=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EA=B3=A0,=20=ED=88=B4=ED=8C=81=EC=9D=B4=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A7=80=EC=A7=80=20=EC=95=8A=EC=95=98=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EC=97=90=EB=A7=8C=20=ED=88=B4=ED=8C=81=EC=9D=84=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/datastore/UserPreferences.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt index 422d0563..220dfa55 100644 --- a/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt +++ b/core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt @@ -89,8 +89,8 @@ class UserPreferences @Inject constructor( .catch { emit(emptyPreferences()) } .map { pref -> val hasTodayFortune = pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null - val tooltipNotShown = pref[Keys.FORTUNE_TOOLTIP_SHOWN] ?: true - hasTodayFortune && tooltipNotShown + val tooltipNotShown = pref[Keys.FORTUNE_TOOLTIP_SHOWN] ?: false + hasTodayFortune && !tooltipNotShown } .distinctUntilChanged() From fdd4fe2546dc769bca41f70603fde08ccabefa91 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Thu, 21 Aug 2025 12:51:58 +0900 Subject: [PATCH 26/28] =?UTF-8?q?[FIX/#246]=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=ED=95=B4=EC=A0=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EC=8B=9C=20earliestIdToday=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt index 371beb55..011dadef 100644 --- a/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt +++ b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt @@ -130,7 +130,7 @@ class AlarmReceiver : BroadcastReceiver() { if (isEarliestAlarmDismissedToday) fortuneRepository.markFirstAlarmDismissedToday() - val isFirstAlarm = alarms.firstOrNull()?.id == notificationId + val isFirstAlarm = earliestIdToday == notificationId analyticsHelper.logEvent( AnalyticsEvent( type = "alarm_dismiss", From 3532b26d1967dff05d11e33615b8a556f1b71173 Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Sun, 7 Sep 2025 06:56:39 +0900 Subject: [PATCH 27/28] =?UTF-8?q?[FEAT/#246]=20=EC=9A=B4=EC=84=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EB=94=A9=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=A1=9C=ED=8B=B0=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable-xhdpi/ic_100_buble.png | Bin 5570 -> 0 bytes .../ic_fortune_delivering_speech_bubble.png | Bin 0 -> 5516 bytes .../ic_fortune_waiting_speech_bubble.png | Bin 0 -> 4249 bytes .../main/res/drawable-xxhdpi/ic_100_buble.png | Bin 8911 -> 0 bytes .../ic_fortune_delivering_speech_bubble.png | Bin 0 -> 8822 bytes .../ic_fortune_waiting_speech_bubble.png | Bin 0 -> 6681 bytes .../src/main/res/drawable/ic_100_buble.png | Bin 2568 -> 0 bytes .../src/main/res/raw/fortune_loading.json | 1 + .../java/com/yapp/fortune/FortuneScreen.kt | 46 +++++++++++++++--- 9 files changed, 40 insertions(+), 7 deletions(-) delete mode 100644 core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png create mode 100644 core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png delete mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png create mode 100644 core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png delete mode 100644 core/designsystem/src/main/res/drawable/ic_100_buble.png create mode 100644 core/designsystem/src/main/res/raw/fortune_loading.json diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png deleted file mode 100644 index d30beec63f8d6f04992b85964925cda5c275ef4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5570 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

PXKFOOi73Z1zJ2T&5_+Q%m8-qlT zqh1z+X#Fpasrme=FY+F;1Kue+%3g_XskK6xDD$>paUh?q8aI5BK@cp5C^-Yeyb~T? zNp7jXEj8I=Z(FClkY^lv+Z>(=+<@YnnQk@vK>gJ_;lx0kH zX%>(=+TwsibPdR51kIDlv9%x9F-%yTaVBW#DTA02svhDVUK0bH%*IkEDC=i&0gQ2A znKQ@c>dxvw8|bT6kR_`}{ZFnf12uM=n`~lOoqHn*$^-hRp2kfu496wJc0$bbFINu( zli-QDo-qPm>*;;%bc(&QyAlyCN{l}gO?rs+t(1y9OE6AAOmh0Jk*GLExPWhgQDKFE+r0Kb6TX z?Oeu(=J}D!jb2Zfdev0!tBGhMdX^&1hoeK0ABMJB*b zE?ynTkCUC=siR3f8Da(|Q{8xghh@e+jkIK>8h~Z^Gz<2dF9Bxe%1~7Qt6h?HIJhOu zg4OxkCFVE&-!2Bz0OUXZ42#J*a1fr?AGhTJetCKTu^}PB!NKi&8<Mq^;D>S`jakJjgE&hK19$)dI diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png new file mode 100644 index 0000000000000000000000000000000000000000..c75d2db33b202716101e90316711fe3ce24b53a3 GIT binary patch literal 5516 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png new file mode 100644 index 0000000000000000000000000000000000000000..a119387dc06bd40387d9e0a5ca50ffd119ff51ed GIT binary patch literal 4249 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 diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png new file mode 100644 index 0000000000000000000000000000000000000000..318d11d6062ce186e0cdaa3354d0750540fa68da GIT binary patch literal 8822 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 literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png new file mode 100644 index 0000000000000000000000000000000000000000..83b665f423f4e63f51a494d41b6b4e0140cffca4 GIT binary patch literal 6681 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 Ilx)KP2cO*({Qv*} literal 0 HcmV?d00001 diff --git a/core/designsystem/src/main/res/drawable/ic_100_buble.png b/core/designsystem/src/main/res/drawable/ic_100_buble.png deleted file mode 100644 index e69978c4f5aad0871e51a2d9d35d1005010c4d11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2568 zcmV+j3itJiP)b@Aqw7x^u48FNLQ^^>n+4u(1OUCAi(d3%}+zR7t&n_F(2QBbQ#jOL74vw zku4JAOfeDuc}NdYSinLo0q52+<6eL6BcVWP!vu6C}hU5pRMZpFt$&M-a&| z*Mx*vq~Lz|{(TIQ9EovaLPvN2`G`eY9@zI#Bt&AI5L?6{Ar?8%w(p}zgv2;4bWbRT zSmXe|ho3+sLt>l=izU7fA@bp_eJ@1<-iJuVCelIINI#?l6z?!tluk%JE9*3*0h)t# zQ$QFqdyd-&uZ`EpdwYT9{J*BL*+3m)3=-o6m~Sqn1@apdVoc#Y!eZ=&v{l(hF@A>C zc4`XL?=gBG_d`0g&vgczzMX%7`mo%mcFt*K|F(VRpg`fAbZ_rk-c2imc?Y;TnDF|# z(ce7&Im+wVu{ut%JZK%i*J`!?2hk`J<22mE>gb~s8Fq4t(+TPeV>`+_>zq$f2=Orr zaiX|TBzI5>;i|Z@dMQK{sa+HT=VI&w&IxW>>by5EPzc?;R{Tp8_lRYDVf)(A{+K5r z{SML z=PfIXZ5UR{kHwJfKDPR1S(rTc4=C;zh0?|Nm6bQJvb;^5N}PM&%!1`GejtSjNk=wnYtO88-|c0kIk(W=C}^buZe_PTWJ$;r+3ho&7W0 z#}0jg(jPgr_MKoh?q950Q5%?SRc~86M&$LS06Uz2SYZrZI z)7t3%Q_nk1j44vPD6M)IVYY)p?rw;T zD6K0B;D+VhuzhZcO?w39af%p4@o9A4G0MmFn5TC9%V-}3=+iNIrwO9F8kmP7cxr#M z{1c2?+;Mrl2Nc41P;w#NM|~moJZ($IQ}Xdx-}1SIJjox+``L}smR%3q?1M`mFeBS2 ztzU18i)vhdJU~o>`3^AZ=Y5Vc1dLo^)UtNh3}%#D2ev!D#`79wZrFKOHS&@ptHbXV zudk1?-qRX&FM<{$HH^gg_CRZimJ}^0@2LJ*OY1Bak2nf|xKO~(ts_eAqxLd}R-f1D zf<=q7hcfJo$KAT~M|q#XxP z#Ps;`PNfZ1!QZd^U7a{xCKBU@kr>||*wJaFZlR1M@UXUV;?hGQ+~e@5glYIAb5%TD z99-zQ-L=oO?shDBe6P1{%kpei%9%zVGzxFuwl1iShgwa;Xa2uHDS}lWEo|Sfqy4d; z`Rhvl0qR??p066pM|kaDVbm>giqZCUqx0Xzx^w~OVA4Y&?p>52f*#!w4+sOx&uf1< zCNDpKS*eHn#tE*6EY2JKQJ1$6jUq8_p8sI^^iYOC`J*w}wXF*-@kIqYC?A*oeG+bY z)$y6SCFf%B81v{S|N& z{I8>O<5=B;nq|Zh9Nx}Z__gpUfa2>DkMUqMZBSW6jGtjtG})Ftl;LZ>Xl7E`N0xk2 zanb=+ zi_k+ki5KG&cvZh_Ovlb^B4+npalbe%?snyvFXOH|;LXET5aYi>G>XJH0YQw}u{Mz& z7bF*C_k-4TRS>rZNj%Zy+6rh<^hCg1`6>RL>5SlGb5wbdE$0+A&WRKB*qyM1ewcTABFS* zgjg<|q&rgxTYgB4Gsdjs`;aJ$#bM|gCyKwZ6`&$BB*vLz@3FWS(!-EGgb?$=mp1(q zg@0HgoG}vPH6jQzEo6Ut*BulVv#e&}&;>!k Date: Sun, 7 Sep 2025 07:02:33 +0900 Subject: [PATCH 28/28] =?UTF-8?q?[REMOVE/#246]=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20`@Auth`,=20`@NoneAuth?= =?UTF-8?q?`,=20`@S3`=20Qualifier=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/network/di/NetworkModule.kt | 41 ++----------------- .../java/com/yapp/network/di/Qualifier.kt | 15 ------- .../com/yapp/data/remote/di/ServiceModule.kt | 3 +- 3 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 core/network/src/main/java/com/yapp/network/di/Qualifier.kt diff --git a/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt b/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt index 10276dfb..25dfb118 100644 --- a/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt +++ b/core/network/src/main/java/com/yapp/network/di/NetworkModule.kt @@ -6,7 +6,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json -import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -39,21 +38,7 @@ object NetworkModule { @Provides @Singleton - @Auth - fun provideAuthOkHttpClient( - loggingInterceptor: HttpLoggingInterceptor, - authInterceptor: Interceptor, - ): OkHttpClient = - OkHttpClient.Builder() - .retryOnConnectionFailure(true) - .addInterceptor(loggingInterceptor) - .addInterceptor(authInterceptor) - .build() - - @Provides - @Singleton - @NoneAuth - fun provideNoneAuthOkHttpClient( + fun provideHttpClient( loggingInterceptor: HttpLoggingInterceptor, ): OkHttpClient = OkHttpClient.Builder() @@ -66,18 +51,8 @@ object NetworkModule { @Provides @Singleton - @Auth - fun provideAuthRetrofit(@Auth okHttpClient: OkHttpClient, buildConfigFieldProvider: BuildConfigFieldProvider): Retrofit = Retrofit.Builder() - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) - .baseUrl(buildConfigFieldProvider.get().baseUrl) - .client(okHttpClient) - .build() - - @Provides - @Singleton - @NoneAuth - fun provideNoneAuthRetrofit( - @NoneAuth okHttpClient: OkHttpClient, + fun provideRetrofit( + okHttpClient: OkHttpClient, buildConfigFieldProvider: BuildConfigFieldProvider, json: Json, ): Retrofit = @@ -86,14 +61,4 @@ object NetworkModule { .baseUrl(buildConfigFieldProvider.get().baseUrl) .client(okHttpClient) .build() - - @Provides - @Singleton - @S3 - fun provideS3Retrofit(@NoneAuth okHttpClient: OkHttpClient, buildConfigFieldProvider: BuildConfigFieldProvider): Retrofit = - Retrofit.Builder() - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) - .baseUrl(buildConfigFieldProvider.get().baseUrl) - .client(okHttpClient) - .build() } diff --git a/core/network/src/main/java/com/yapp/network/di/Qualifier.kt b/core/network/src/main/java/com/yapp/network/di/Qualifier.kt deleted file mode 100644 index 68817100..00000000 --- a/core/network/src/main/java/com/yapp/network/di/Qualifier.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.yapp.network.di - -import javax.inject.Qualifier - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class NoneAuth - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Auth - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class S3 diff --git a/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt b/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt index 458db14b..be0e97f5 100644 --- a/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt +++ b/data/src/main/java/com/yapp/data/remote/di/ServiceModule.kt @@ -1,7 +1,6 @@ package com.yapp.data.remote.di import com.yapp.data.remote.service.ApiService -import com.yapp.network.di.NoneAuth import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,6 +13,6 @@ import javax.inject.Singleton object ServiceModule { @Provides @Singleton - fun providesApiService(@NoneAuth retrofit: Retrofit): ApiService = + fun providesApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java) }