diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d56d2894..6c350ede 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,6 +8,7 @@ plugins {
android {
namespace = "com.yapp.orbit"
+ compileSdk = 35
defaultConfig {
versionCode = 6
@@ -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..6eabb63b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -46,9 +46,7 @@
-
+
@@ -80,5 +78,15 @@
+
+
+
+
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/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())
}
}
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/receivers/AlarmInteractionActivityReceiver.kt b/core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmInteractionActivityReceiver.kt
index 39de3171..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
@@ -11,10 +11,9 @@ 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.flow.first
import kotlinx.coroutines.launch
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@AndroidEntryPoint
@@ -42,24 +41,38 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity)
missionCount != -1
)
- if (!hasValidMissionData) return
-
- CoroutineScope(Dispatchers.IO).launch {
- val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull()
- val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
-
- val shouldLaunchMission = fortuneDate != todayDate
+ val pending = goAsync()
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ if (!hasValidMissionData) {
+ val hasUnseenFortune = withContext(Dispatchers.IO) {
+ 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
+ }
- if (shouldLaunchMission) {
- context?.let {
+ 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)
- }
- it.startActivity(missionIntent)
+ val missionIntent =
+ Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ setPackage(ctx.packageName)
+ }
+ ctx.startActivity(missionIntent)
}
+ } finally {
+ pending.finish()
}
}
}
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..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
@@ -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
@@ -19,8 +20,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
@@ -105,17 +106,31 @@ 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().sortedBy { it.isAlarmActive }
- val isFirstAlarm = alarms.firstOrNull()?.id == notificationId
+ val alarms = alarmUseCase.getAllAlarms().first()
+
+ val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET
+
+ fun Alarm.ringsToday(): Boolean {
+ if (repeatDays == 0) return true
+
+ val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay()
+ return (repeatDays and todayAlarmDay.bitValue) != 0
+ }
+ val earliestIdToday: Long? = alarms
+ .asSequence()
+ .filter { (it.isAlarmActive || it.id == notificationId) && it.ringsToday() }
+ .sortedWith(compareBy({ it.hour }, { it.minute }, { it.second }))
+ .firstOrNull()
+ ?.id
+
+ val isEarliestAlarmDismissedToday =
+ !isSnoozeId && (earliestIdToday == notificationId)
+
+ if (isEarliestAlarmDismissedToday) fortuneRepository.markFirstAlarmDismissedToday()
+
+ val isFirstAlarm = earliestIdToday == notificationId
analyticsHelper.logEvent(
AnalyticsEvent(
type = "alarm_dismiss",
@@ -126,12 +141,12 @@ class AlarmReceiver : BroadcastReceiver() {
),
)
- val existingId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull()
- if (existingId == null) {
- fortuneRepository.saveFirstDismissedAlarmId(notificationId)
- } else if (existingId != notificationId) {
- fortuneRepository.clearDismissedAlarmId()
- }
+ sendBroadCastToCloseAlarmInteractionActivity(
+ context = context,
+ notificationId = notificationId,
+ missionType = missionType,
+ missionCount = missionCount,
+ )
}
Toast.makeText(context, "알람이 해제되었어요", Toast.LENGTH_SHORT).show()
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/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 {
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..220dfa55 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
@@ -26,15 +25,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 FIRST_DISMISSED_ALARM_ID = longPreferencesKey("first_dismissed_alarm_id")
- val DISMISSED_DATE = stringPreferencesKey("dismissed_date")
+ 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_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)
+
val userIdFlow: Flow = dataStore.data
.catch { emit(emptyPreferences()) }
.map { it[Keys.USER_ID] }
@@ -70,108 +76,150 @@ class UserPreferences @Inject constructor(
.map { it[Keys.FORTUNE_SCORE] }
.distinctUntilChanged()
- val hasNewFortuneFlow: Flow = dataStore.data
+ val hasUnseenFortuneFlow: 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 ->
+ pref[Keys.FORTUNE_DATE] == today() &&
+ pref[Keys.FORTUNE_ID] != null &&
+ (pref[Keys.FORTUNE_SEEN] != true)
}
.distinctUntilChanged()
- val firstDismissedAlarmIdFlow: Flow = dataStore.data
+ val shouldShowFortuneToolTipFlow: 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 { pref ->
+ val hasTodayFortune = pref[Keys.FORTUNE_DATE] == today() && pref[Keys.FORTUNE_ID] != null
+ val tooltipNotShown = pref[Keys.FORTUNE_TOOLTIP_SHOWN] ?: false
+ hasTodayFortune && !tooltipNotShown
+ }
+ .distinctUntilChanged()
+
+ val isFortuneCreatingFlow: Flow = dataStore.data
+ .catch { emit(emptyPreferences()) }
+ .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 ->
+ 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 { 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 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 saveFortuneImageId(imageResId: Int) {
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_IMAGE_ID] = imageResId
+ suspend fun markFortuneCreating() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_CREATING] = true
+ pref[Keys.FORTUNE_FAILED] = false
}
}
- suspend fun saveFortuneScore(score: Int) {
- dataStore.edit { preferences ->
- preferences[Keys.FORTUNE_SCORE] = score
+ 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
+
+ if (isNewForToday) {
+ pref[Keys.FORTUNE_SEEN] = false
+ pref[Keys.FORTUNE_TOOLTIP_SHOWN] = false
+ }
}
}
- 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 markFortuneFailed() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_CREATING] = false
+ pref[Keys.FORTUNE_FAILED] = true
}
}
- suspend fun setOnboardingCompleted() {
- dataStore.edit { preferences ->
- preferences[Keys.ONBOARDING_COMPLETED] = true
+ suspend fun markFortuneSeen() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_SEEN] = true
}
}
- suspend fun clearDismissedAlarmId() {
- dataStore.edit { preferences ->
- preferences.remove(Keys.FIRST_DISMISSED_ALARM_ID)
- preferences.remove(Keys.DISMISSED_DATE)
+ suspend fun markFortuneTooltipShown() {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_TOOLTIP_SHOWN] = true
}
}
- suspend fun clearUserData() {
- dataStore.edit { preferences ->
- preferences.clear()
+ suspend fun saveFortuneImageId(imageResId: Int) {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_IMAGE_ID] = imageResId
}
}
- 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 saveFortuneScore(score: Int) {
+ dataStore.edit { pref ->
+ pref[Keys.FORTUNE_SCORE] = score
+ }
+ }
+
+ 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() }
+ }
+
+ 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/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 d30beec6..00000000
Binary files a/core/designsystem/src/main/res/drawable-xhdpi/ic_100_buble.png and /dev/null differ
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 00000000..c75d2db3
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_delivering_speech_bubble.png differ
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 00000000..a119387d
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/ic_fortune_waiting_speech_bubble.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png b/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png
deleted file mode 100644
index 469a7b3f..00000000
Binary files a/core/designsystem/src/main/res/drawable-xxhdpi/ic_100_buble.png and /dev/null differ
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 00000000..318d11d6
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_delivering_speech_bubble.png differ
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 00000000..83b665f4
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/ic_fortune_waiting_speech_bubble.png differ
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 e69978c4..00000000
Binary files a/core/designsystem/src/main/res/drawable/ic_100_buble.png and /dev/null differ
diff --git a/core/designsystem/src/main/res/raw/fortune_loading.json b/core/designsystem/src/main/res/raw/fortune_loading.json
new file mode 100644
index 00000000..cb570812
--- /dev/null
+++ b/core/designsystem/src/main/res/raw/fortune_loading.json
@@ -0,0 +1 @@
+{"nm":"컴포지션 2","h":500,"w":700,"meta":{"g":"@lottiefiles/toolkit-js 0.66.4","tc":"#202f44"},"layers":[{"ty":2,"nm":"Group 1948760243.png","sr":1,"st":1,"op":45,"ip":0,"ln":"2856","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[151.5,137]},"s":{"a":0,"k":[61.6,61.6,101.65]},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[130.5,159,0],"t":0},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[167.139,178.955,0],"t":15},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[233.8,193.529,0],"t":31},{"s":[285.485,192.267,0],"t":45}]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[17.4],"t":0},{"s":[32.4],"t":45}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"1","ind":1},{"ty":2,"nm":"Group 1948760248-1.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2855","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[22,121.064]},"s":{"a":0,"k":[70.27,70.27,89.655]},"p":{"a":0,"k":[386,148,0]},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-9.304],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":45},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[10],"t":48},{"s":[0],"t":51}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"2","ind":2},{"ty":2,"nm":"Vector 27856.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2854","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[93.94,9.052]},"s":{"a":0,"k":[75.628,72.572,100]},"p":{"a":0,"k":[310.5,242.5,0]},"r":{"a":0,"k":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"3","ind":3},{"ty":2,"nm":"Group 1948760248.png","sr":1,"st":0,"op":45,"ip":0,"ln":"2853","hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[132.5,232]},"s":{"a":0,"k":[76.4,76.4,76.4]},"p":{"a":0,"k":[370,256,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"refId":"4","ind":4}],"v":"5.7.0","fr":30,"op":45,"ip":0,"assets":[{"id":"1","e":1,"w":303,"h":274,"p":"data:image/webp;base64,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","u":""},{"id":"2","e":1,"w":100,"h":148,"p":"data:image/webp;base64,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","u":""},{"id":"3","e":1,"w":154,"h":124,"p":"data:image/webp;base64,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","u":""},{"id":"4","e":1,"w":265,"h":464,"p":"data:image/webp;base64,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","u":""}]}
\ No newline at end of file
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/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..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
@@ -7,14 +7,21 @@ interface FortuneLocalDataSource {
val fortuneDateFlow: Flow
val fortuneImageIdFlow: Flow
val fortuneScoreFlow: Flow
- val hasNewFortuneFlow: Flow
- val firstDismissedAlarmIdFlow: Flow
+ val hasUnseenFortuneFlow: Flow
+ val shouldShowFortuneToolTipFlow: Flow
+ val isFortuneCreatingFlow: Flow
+ val isFortuneFailedFlow: Flow
+ val isFirstAlarmDismissedTodayFlow: Flow
- suspend fun saveFortuneId(fortuneId: Long)
- suspend fun markFortuneAsChecked()
+ suspend fun tryMarkFortuneCreating(): Boolean
+ 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 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 6dbe10f9..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
@@ -11,15 +11,34 @@ 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 isFortuneCreatingFlow = userPreferences.isFortuneCreatingFlow
+ override val isFortuneFailedFlow = userPreferences.isFortuneFailedFlow
+ override val isFirstAlarmDismissedTodayFlow = userPreferences.isFirstAlarmDismissedTodayFlow
+
+ override suspend fun tryMarkFortuneCreating(): Boolean {
+ return userPreferences.tryMarkFortuneCreating()
+ }
+
+ override suspend fun markFortuneCreating() {
+ userPreferences.markFortuneCreating()
+ }
+
+ override suspend fun markFortuneCreated(fortuneId: Long) {
+ userPreferences.markFortuneCreated(fortuneId)
+ }
- override suspend fun saveFortuneId(fortuneId: Long) {
- userPreferences.saveFortuneId(fortuneId)
+ override suspend fun markFortuneFailed() {
+ userPreferences.markFortuneFailed()
}
- override suspend fun markFortuneAsChecked() {
- userPreferences.markFortuneAsChecked()
+ override suspend fun markFortuneSeen() {
+ userPreferences.markFortuneSeen()
+ }
+
+ override suspend fun markFortuneTooltipShown() {
+ userPreferences.markFortuneTooltipShown()
}
override suspend fun saveFortuneImageId(imageResId: Int) {
@@ -30,15 +49,11 @@ class FortuneLocalDataSourceImpl @Inject constructor(
userPreferences.saveFortuneScore(score)
}
- override suspend fun saveFirstDismissedAlarmId(alarmId: Long) {
- userPreferences.saveFirstDismissedAlarmId(alarmId)
- }
-
- override suspend fun clearDismissedAlarmId() {
- userPreferences.clearDismissedAlarmId()
+ override suspend fun markFirstAlarmDismissedToday() {
+ userPreferences.markFirstAlarmDismissedToday()
}
- override suspend fun clearFortuneId() {
- userPreferences.clearFortuneId()
+ override suspend fun clearFortuneData() {
+ userPreferences.clearFortuneData()
}
}
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)
}
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..23271c5f 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,36 @@ 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 isFortuneCreatingFlow: Flow = fortuneLocalDataSource.isFortuneCreatingFlow
+ override val isFortuneFailedFlow: Flow = fortuneLocalDataSource.isFortuneFailedFlow
+ override val isFirstAlarmDismissedTodayFlow: Flow = fortuneLocalDataSource.isFirstAlarmDismissedTodayFlow
- override suspend fun saveFortuneId(fortuneId: Long) = fortuneLocalDataSource.saveFortuneId(fortuneId)
- override suspend fun markFortuneAsChecked() = fortuneLocalDataSource.markFortuneAsChecked()
+ 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()
+ 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 markFirstAlarmDismissedToday() = fortuneLocalDataSource.markFirstAlarmDismissedToday()
+
+ 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/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 {
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..89cc4a67 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,24 @@ interface FortuneRepository {
val fortuneDateFlow: Flow
val fortuneImageIdFlow: Flow
val fortuneScoreFlow: Flow
- val hasNewFortuneFlow: Flow
- val firstDismissedAlarmIdFlow: Flow
+ val hasUnseenFortuneFlow: Flow
+ val shouldShowFortuneToolTipFlow: Flow
+ val isFortuneCreatingFlow: Flow
+ val isFortuneFailedFlow: Flow
+ val isFirstAlarmDismissedTodayFlow: Flow
- suspend fun saveFortuneId(fortuneId: Long)
- suspend fun markFortuneAsChecked()
+ suspend fun tryMarkFortuneCreating(): Boolean
+ 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 markFirstAlarmDismissedToday()
+
+ suspend fun clearFortuneData()
+
suspend fun postFortune(userId: Long): Result
suspend fun getFortune(fortuneId: Long): Result
}
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..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
@@ -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) != MissionType.NONE)
}
}
diff --git a/feature/fortune/build.gradle.kts b/feature/fortune/build.gradle.kts
index ae450155..543510e8 100644
--- a/feature/fortune/build.gradle.kts
+++ b/feature/fortune/build.gradle.kts
@@ -12,10 +12,14 @@ 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)
+ testImplementation(libs.androidx.work.testing)
+ androidTestImplementation(libs.androidx.work.testing)
implementation(projects.domain)
implementation(projects.core.media)
}
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()
diff --git a/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt b/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
index f5109a3b..1eb97abc 100644
--- a/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
+++ b/feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt
@@ -3,11 +3,13 @@ package com.yapp.fortune
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
@@ -15,6 +17,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -34,6 +37,7 @@ import com.yapp.fortune.component.FortuneTopAppBar
import com.yapp.fortune.component.SlidingIndicator
import com.yapp.fortune.page.FortunePager
import com.yapp.ui.component.lottie.LottieAnimation
+import kotlinx.coroutines.delay
import java.math.BigDecimal
import java.math.RoundingMode
@@ -184,21 +188,49 @@ fun FortuneScreen(
@Composable
fun FortuneLoadingScreen() {
+ var isDelivering by remember { mutableStateOf(false) }
+
+ LaunchedEffect(Unit) {
+ while (true) {
+ delay(2000)
+ isDelivering = !isDelivering
+ }
+ }
+
Box(
modifier = Modifier
.fillMaxSize()
.background(OrbitTheme.colors.gray_900.copy(alpha = 0.7f)),
contentAlignment = Alignment.Center,
) {
- LottieAnimation(
- modifier = Modifier
- .size(70.dp),
- resId = core.designsystem.R.raw.star_loading,
- )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(6.dp),
+ ) {
+ val imageRes = if (isDelivering) {
+ core.designsystem.R.drawable.ic_fortune_delivering_speech_bubble
+ } else {
+ core.designsystem.R.drawable.ic_fortune_waiting_speech_bubble
+ }
+ Image(
+ painter = painterResource(id = imageRes),
+ contentDescription = null,
+ )
+
+ LottieAnimation(
+ modifier = Modifier
+ .width(375.dp)
+ .height(267.dp),
+ resId = core.designsystem.R.raw.fortune_loading,
+ )
+ }
}
}
@Composable
@Preview
-fun FortuneRoutePreview() {
+private fun FortuneLoadingScreenPreview() {
+ OrbitTheme {
+ FortuneLoadingScreen()
+ }
}
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..003aa09d 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
@@ -16,8 +17,6 @@ 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
@HiltViewModel
@@ -30,7 +29,7 @@ class FortuneViewModel @Inject constructor(
override val container: Container = container(
initialState = FortuneContract.State(),
) {
- loadFortune()
+ observeFortune()
}
fun processAction(action: FortuneContract.Action) {
@@ -50,14 +49,32 @@ 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.isFirstAlarmDismissedTodayFlow,
+ fortuneRepository.isFortuneCreatingFlow,
+ ) { 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, isFirstAlarmDismissedToday)
+ }
+ else -> {
+ reduce { state.copy(isLoading = false) }
+ }
+ }
+ }
}
- private fun fetchAndUpdateFortune(fortuneId: Long, firstDismissedAlarmId: Long?, fortuneDate: String?) = intent {
+ private fun fetchAndUpdateFortune(
+ fortuneId: Long,
+ isFirstAlarmDismissedToday: Boolean,
+ ) = intent {
reduce { state.copy(isLoading = true) }
fortuneRepository.getFortune(fortuneId).onSuccess { fortune ->
@@ -65,8 +82,9 @@ 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) && (firstDismissedAlarmId != null)
+
+ fortuneRepository.markFortuneSeen()
+
reduce {
state.copy(
isLoading = false,
@@ -75,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/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/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 -> {}
}
}
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..751ae536
--- /dev/null
+++ b/feature/fortune/src/main/java/com/yapp/fortune/worker/PostFortuneWorker.kt
@@ -0,0 +1,54 @@
+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()
+ if (hasUnseenFortune) return Result.success()
+
+ val acquired = fortuneRepository.tryMarkFortuneCreating()
+ if (!acquired) return Result.success()
+
+ val userId = userInfoRepository.userIdFlow.firstOrNull()
+ ?: run {
+ fortuneRepository.markFortuneAsFailed()
+ 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/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(
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)
- }
}
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" }