Skip to content

Commit a7723cd

Browse files
authored
Merge pull request #247 from YAPP-Github/feat/#246-fortune-api-call-timing
[FEAT] 운세 생성 API 호출 시점 변경
2 parents e8f852f + 500862c commit a7723cd

44 files changed

Lines changed: 509 additions & 334 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88

99
android {
1010
namespace = "com.yapp.orbit"
11+
compileSdk = 35
1112

1213
defaultConfig {
1314
versionCode = 6
@@ -53,4 +54,6 @@ dependencies {
5354
implementation(libs.firebase.crashlytics)
5455
implementation(libs.play.services.ads)
5556
implementation(libs.kotlin.reflect)
57+
implementation(libs.hilt.worker)
58+
implementation(libs.androidx.work.runtime)
5659
}

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@
4646
<action android:name="android.intent.action.VIEW" />
4747
<category android:name="android.intent.category.DEFAULT" />
4848
<category android:name="android.intent.category.BROWSABLE" />
49-
<data
50-
android:scheme="orbitapp"
51-
android:host="mission"/>
49+
<data android:scheme="orbitapp" />
5250
</intent-filter>
5351
</activity>
5452

@@ -80,5 +78,15 @@
8078
<service
8179
android:name="com.yapp.alarm.services.AlarmService"
8280
android:foregroundServiceType="mediaPlayback" />
81+
82+
<provider
83+
android:name="androidx.startup.InitializationProvider"
84+
android:authorities="${applicationId}.androidx-startup"
85+
android:exported="false"
86+
tools:node="merge">
87+
<meta-data
88+
android:name="androidx.work.WorkManagerInitializer"
89+
tools:node="remove" />
90+
</provider>
8391
</application>
8492
</manifest>
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package com.yapp.orbit
22

33
import android.app.Application
4+
import androidx.hilt.work.HiltWorkerFactory
5+
import androidx.work.Configuration
46
import com.google.android.gms.ads.MobileAds
57
import dagger.hilt.android.HiltAndroidApp
8+
import javax.inject.Inject
69

710
@HiltAndroidApp
8-
class OrbitApplication : Application() {
11+
class OrbitApplication() : Application(), Configuration.Provider {
12+
13+
@Inject lateinit var workerFactory: HiltWorkerFactory
14+
915
override fun onCreate() {
1016
super.onCreate()
1117
MobileAds.initialize(this)
1218
}
19+
20+
override val workManagerConfiguration: Configuration
21+
get() = Configuration.Builder()
22+
.setWorkerFactory(workerFactory)
23+
.build()
1324
}

build-logic/src/main/java/com/yapp/convention/HiltAndroid.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ internal fun Project.configureHiltAndroid() {
1414
dependencies {
1515
"implementation"(libs.findLibrary("hilt.android").get())
1616
"ksp"(libs.findLibrary("hilt.android.compiler").get())
17+
"ksp"(libs.findLibrary("androidx-hilt-compiler").get())
1718
"implementation"(libs.findLibrary("hilt-navigation-compose").get())
19+
"implementation"(libs.findLibrary("hilt-worker").get())
1820
}
1921
}
2022

build-logic/src/main/java/orbit.android.feature.gradle.kts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import com.yapp.convention.configureHiltAndroid
21
import com.yapp.convention.libs
32

43
plugins {
54
id("orbit.android.library")
65
id("orbit.android.compose")
76
}
87

9-
configureHiltAndroid()
10-
118
dependencies {
129
implementation(project(":core:designsystem"))
1310
implementation(project(":core:ui"))

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

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import com.yapp.domain.repository.FortuneRepository
1111
import dagger.hilt.android.AndroidEntryPoint
1212
import kotlinx.coroutines.CoroutineScope
1313
import kotlinx.coroutines.Dispatchers
14-
import kotlinx.coroutines.flow.firstOrNull
14+
import kotlinx.coroutines.flow.first
1515
import kotlinx.coroutines.launch
16-
import java.time.LocalDate
17-
import java.time.format.DateTimeFormatter
16+
import kotlinx.coroutines.withContext
1817
import javax.inject.Inject
1918

2019
@AndroidEntryPoint
@@ -42,24 +41,38 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity)
4241
missionCount != -1
4342
)
4443

45-
if (!hasValidMissionData) return
46-
47-
CoroutineScope(Dispatchers.IO).launch {
48-
val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull()
49-
val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
50-
51-
val shouldLaunchMission = fortuneDate != todayDate
44+
val pending = goAsync()
45+
CoroutineScope(Dispatchers.Main).launch {
46+
try {
47+
if (!hasValidMissionData) {
48+
val hasUnseenFortune = withContext(Dispatchers.IO) {
49+
fortuneRepository.hasUnseenFortuneFlow.first()
50+
}
51+
if (hasUnseenFortune) {
52+
context?.let { ctx ->
53+
val uri = "orbitapp://fortune".toUri()
54+
val fortuneIntent = Intent(Intent.ACTION_VIEW, uri).apply {
55+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
56+
setPackage(ctx.packageName)
57+
}
58+
ctx.startActivity(fortuneIntent)
59+
}
60+
}
61+
return@launch
62+
}
5263

53-
if (shouldLaunchMission) {
54-
context?.let {
64+
context?.let { ctx ->
5565
val uriString =
5666
"orbitapp://mission?notificationId=$notificationId&missionType=${missionType.value}&missionCount=$missionCount"
57-
val missionIntent = Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
58-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
59-
setPackage(it.packageName)
60-
}
61-
it.startActivity(missionIntent)
67+
val missionIntent =
68+
Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
69+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
70+
setPackage(ctx.packageName)
71+
}
72+
ctx.startActivity(missionIntent)
6273
}
74+
} finally {
75+
pending.finish()
6376
}
6477
}
6578
}

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

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ import com.yapp.alarm.services.AlarmService
1212
import com.yapp.analytics.AnalyticsEvent
1313
import com.yapp.analytics.AnalyticsHelper
1414
import com.yapp.domain.model.Alarm
15+
import com.yapp.domain.model.toAlarmDay
1516
import com.yapp.domain.model.toTimeString
1617
import com.yapp.domain.repository.FortuneRepository
1718
import com.yapp.domain.usecase.AlarmUseCase
1819
import dagger.hilt.android.AndroidEntryPoint
1920
import kotlinx.coroutines.CoroutineScope
2021
import kotlinx.coroutines.Dispatchers
2122
import kotlinx.coroutines.flow.first
22-
import kotlinx.coroutines.flow.firstOrNull
2323
import kotlinx.coroutines.launch
24+
import java.time.LocalDate
2425
import java.time.LocalDateTime
2526
import javax.inject.Inject
2627

@@ -105,17 +106,31 @@ class AlarmReceiver : BroadcastReceiver() {
105106
androidAlarmScheduler.cancelSnoozedAlarm(notificationId)
106107
context.stopService(alarmServiceIntent)
107108

108-
sendBroadCastToCloseAlarmInteractionActivity(
109-
context = context,
110-
notificationId = notificationId,
111-
missionType = missionType,
112-
missionCount = missionCount,
113-
)
114-
115109
CoroutineScope(Dispatchers.IO).launch {
116-
val alarms = alarmUseCase.getAllAlarms().first().sortedBy { it.isAlarmActive }
117-
val isFirstAlarm = alarms.firstOrNull()?.id == notificationId
110+
val alarms = alarmUseCase.getAllAlarms().first()
111+
112+
val isSnoozeId = notificationId >= AlarmConstants.SNOOZE_ID_OFFSET
113+
114+
fun Alarm.ringsToday(): Boolean {
115+
if (repeatDays == 0) return true
116+
117+
val todayAlarmDay = LocalDate.now().dayOfWeek.toAlarmDay()
118+
return (repeatDays and todayAlarmDay.bitValue) != 0
119+
}
118120

121+
val earliestIdToday: Long? = alarms
122+
.asSequence()
123+
.filter { (it.isAlarmActive || it.id == notificationId) && it.ringsToday() }
124+
.sortedWith(compareBy({ it.hour }, { it.minute }, { it.second }))
125+
.firstOrNull()
126+
?.id
127+
128+
val isEarliestAlarmDismissedToday =
129+
!isSnoozeId && (earliestIdToday == notificationId)
130+
131+
if (isEarliestAlarmDismissedToday) fortuneRepository.markFirstAlarmDismissedToday()
132+
133+
val isFirstAlarm = earliestIdToday == notificationId
119134
analyticsHelper.logEvent(
120135
AnalyticsEvent(
121136
type = "alarm_dismiss",
@@ -126,12 +141,12 @@ class AlarmReceiver : BroadcastReceiver() {
126141
),
127142
)
128143

129-
val existingId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull()
130-
if (existingId == null) {
131-
fortuneRepository.saveFirstDismissedAlarmId(notificationId)
132-
} else if (existingId != notificationId) {
133-
fortuneRepository.clearDismissedAlarmId()
134-
}
144+
sendBroadCastToCloseAlarmInteractionActivity(
145+
context = context,
146+
notificationId = notificationId,
147+
missionType = missionType,
148+
missionCount = missionCount,
149+
)
135150
}
136151

137152
Toast.makeText(context, "알람이 해제되었어요", Toast.LENGTH_SHORT).show()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.yapp.alarm.scheduler
2+
3+
interface PostFortuneTaskScheduler {
4+
fun enqueueOnceForToday()
5+
}

core/alarm/src/main/java/com/yapp/alarm/services/AlarmService.kt

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,18 @@ import com.yapp.alarm.pendingIntent.interaction.createAlarmAlertPendingIntent
2323
import com.yapp.alarm.pendingIntent.interaction.createAlarmDismissPendingIntent
2424
import com.yapp.alarm.pendingIntent.interaction.createAlarmSnoozePendingIntent
2525
import com.yapp.alarm.pendingIntent.interaction.createNavigateToMissionPendingIntent
26+
import com.yapp.alarm.scheduler.PostFortuneTaskScheduler
2627
import com.yapp.domain.model.Alarm
2728
import com.yapp.domain.model.AlarmDay
2829
import com.yapp.domain.model.MissionType
29-
import com.yapp.domain.repository.FortuneRepository
3030
import com.yapp.domain.usecase.AlarmUseCase
3131
import com.yapp.media.sound.SoundPlayer
3232
import dagger.hilt.android.AndroidEntryPoint
3333
import kotlinx.coroutines.CoroutineScope
3434
import kotlinx.coroutines.Dispatchers
3535
import kotlinx.coroutines.SupervisorJob
3636
import kotlinx.coroutines.cancel
37-
import kotlinx.coroutines.flow.firstOrNull
3837
import kotlinx.coroutines.launch
39-
import java.time.LocalDate
40-
import java.time.format.DateTimeFormatter
4138
import javax.inject.Inject
4239

4340
@AndroidEntryPoint
@@ -55,7 +52,7 @@ class AlarmService : Service() {
5552
lateinit var androidAlarmScheduler: AndroidAlarmScheduler
5653

5754
@Inject
58-
lateinit var fortuneRepository: FortuneRepository
55+
lateinit var postFortuneTaskScheduler: PostFortuneTaskScheduler
5956

6057
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
6158

@@ -82,7 +79,7 @@ class AlarmService : Service() {
8279
super.onDestroy()
8380
}
8481

85-
private suspend fun handleIntent(intent: Intent) {
82+
private fun handleIntent(intent: Intent) {
8683
val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
8784
intent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
8885
} else {
@@ -124,16 +121,14 @@ class AlarmService : Service() {
124121
if (isOneTimeAlarm) {
125122
turnOffAlarm(alarmId = notificationId)
126123
}
124+
125+
postFortuneTaskScheduler.enqueueOnceForToday()
127126
}
128127

129-
private suspend fun shouldNavigateToMission(
128+
private fun shouldNavigateToMission(
130129
missionType: MissionType,
131130
): Boolean {
132-
if (missionType == MissionType.NONE) return false
133-
134-
val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull()
135-
val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)
136-
return fortuneDate != todayDate
131+
return missionType != MissionType.NONE
137132
}
138133

139134
private fun createNotification(alarm: Alarm, shouldNavigateToMission: Boolean): Notification {

0 commit comments

Comments
 (0)