Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import android.widget.Toast
import com.yapp.alarm.AlarmConstants
import com.yapp.alarm.AlarmHelper
import com.yapp.alarm.services.AlarmService
import com.yapp.datastore.UserPreferences
import com.yapp.domain.model.Alarm
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.LocalDateTime
import javax.inject.Inject

Expand All @@ -20,6 +25,9 @@ class AlarmReceiver : BroadcastReceiver() {
@Inject
lateinit var alarmHelper: AlarmHelper

@Inject
lateinit var userPreferences: UserPreferences

override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
intent ?: return
Expand All @@ -46,6 +54,12 @@ class AlarmReceiver : BroadcastReceiver() {

AlarmConstants.ACTION_ALARM_DISMISSED -> {
Log.d("AlarmReceiver", "Alarm Dismissed")
val alarmId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
if (alarmId != -1L) {
handleFirstAlarmDismissed(context, alarmId)
} else {
Log.e("AlarmReceiver", "알람 ID 수신 실패")
}
context.stopService(alarmServiceIntent)
sendBroadCastToCloseAlarmInteractionActivity(context)

Expand Down Expand Up @@ -92,6 +106,19 @@ class AlarmReceiver : BroadcastReceiver() {
alarmHelper.scheduleAlarm(updatedAlarm)
}

private fun handleFirstAlarmDismissed(context: Context, alarmId: Long) {
CoroutineScope(Dispatchers.IO).launch {
val existingId = userPreferences.firstDismissedAlarmIdFlow.firstOrNull()
if (existingId == null) {
// 첫 번째 알람 해제 기록
userPreferences.saveFirstDismissedAlarmId(alarmId)
} else if (existingId != alarmId) {
// 두 번째 알람 해제 감지 - 기존 기록 삭제
userPreferences.clearDismissedAlarmId()
}
}
}

private fun sendBroadCastToCloseAlarmInteractionActivity(context: Context) {
Log.d("AlarmReceiver", "Send Broadcast to close Alarm Interaction Activity")
val alarmAlertActivityCloseIntent =
Expand Down
37 changes: 37 additions & 0 deletions core/datastore/src/main/java/com/yapp/datastore/UserPreferences.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,6 +31,8 @@ class UserPreferences @Inject constructor(
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 userIdFlow: Flow<Long?> = dataStore.data
Expand Down Expand Up @@ -77,6 +80,20 @@ class UserPreferences @Inject constructor(
}
.distinctUntilChanged()

val firstDismissedAlarmIdFlow: Flow<Long?> = 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
}
}
.distinctUntilChanged()

suspend fun saveUserId(userId: Long) {
dataStore.edit { preferences ->
preferences[Keys.USER_ID] = userId
Expand Down Expand Up @@ -116,12 +133,32 @@ class UserPreferences @Inject constructor(
}
}

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 setOnboardingCompleted() {
dataStore.edit { preferences ->
preferences[Keys.ONBOARDING_COMPLETED] = true
}
}

suspend fun clearDismissedAlarmId() {
dataStore.edit { preferences ->
preferences.remove(Keys.FIRST_DISMISSED_ALARM_ID)
preferences.remove(Keys.DISMISSED_DATE)
}
}

suspend fun clearUserData() {
dataStore.edit { preferences ->
preferences.clear()
Expand Down
1 change: 1 addition & 0 deletions feature/alarm-interaction/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation(projects.core.alarm)
implementation(projects.core.media)
implementation(projects.domain)
implementation(projects.core.datastore)
implementation(libs.orbit.core)
implementation(libs.orbit.compose)
implementation(libs.orbit.viewmodel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fun FortuneRoute(
state = state,
pagerState = pagerState,
onNextStep = { viewModel.onAction(FortuneContract.Action.NextStep) },
onNavigateToHome = { viewModel.onAction(FortuneContract.Action.NavigateToHome) },
onCloseClick = { viewModel.onAction(FortuneContract.Action.NavigateToHome) },
)
}
Expand All @@ -55,6 +56,7 @@ fun FortuneScreen(
state: FortuneContract.State,
pagerState: PagerState,
onNextStep: () -> Unit,
onNavigateToHome: () -> Unit,
onCloseClick: () -> Unit,
) {
val backgroundRes = when (state.currentStep) {
Expand Down Expand Up @@ -93,7 +95,7 @@ fun FortuneScreen(
activeColor = OrbitTheme.colors.white,
)

FortunePager(state, pagerState, onNextStep)
FortunePager(state, pagerState, onNextStep, onNavigateToHome)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import kotlinx.coroutines.launch
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.postSideEffect
import org.orbitmvi.orbit.syntax.simple.reduce
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import javax.inject.Inject

@HiltViewModel
Expand All @@ -33,18 +35,21 @@ class FortuneViewModel @Inject constructor(
init {
viewModelScope.launch {
val fortuneId = userPreferences.fortuneIdFlow.firstOrNull()
fortuneId?.let { getFortune(it) }
val firstDismissedAlarmId = userPreferences.firstDismissedAlarmIdFlow.firstOrNull()
val fortuneDate = userPreferences.fortuneDateFlow.firstOrNull()
fortuneId?.let { getFortune(it, firstDismissedAlarmId, fortuneDate) }
}
}

private fun getFortune(fortuneId: Long) = intent {
private fun getFortune(fortuneId: Long, firstDismissedAlarmId: Long?, fortuneDate: String?) = intent {
updateState { copy(isLoading = true) }

fortuneRepository.getFortune(fortuneId).onSuccess { fortune ->
val savedImageId = userPreferences.fortuneImageIdFlow.firstOrNull()
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)
updateState {
copy(
isLoading = false,
Expand All @@ -53,6 +58,7 @@ class FortuneViewModel @Inject constructor(
avgFortuneScore = fortune.avgFortuneScore,
fortunePages = fortune.toFortunePages(),
fortuneImageId = imageId,
hasReward = hasReward,
)
}
}.onFailure { error ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.yapp.ui.utils.paddingForScreenPercentage
fun FortuneCompletePage(
hasReward: Boolean,
onCompleteClick: () -> Unit,
onNavigateToHome: () -> Unit,
) {
val message = if (hasReward) {
"첫 알람에 잘 일어났네!\n보상으로 행운 부적을 줄게"
Expand Down Expand Up @@ -80,7 +81,13 @@ fun FortuneCompletePage(
OrbitButton(
label = if (hasReward) "부적 보러가기" else "완료",
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
onClick = onCompleteClick,
onClick = {
if (hasReward) {
onCompleteClick()
} else {
onNavigateToHome()
}
},
enabled = true,
)
}
Expand All @@ -90,5 +97,5 @@ fun FortuneCompletePage(
@Composable
@Preview
fun FortuneCompletePagePreview() {
FortuneCompletePage(hasReward = true, onCompleteClick = {})
FortuneCompletePage(hasReward = true, onCompleteClick = {}, onNavigateToHome = {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fun FortunePager(
state: FortuneContract.State,
pagerState: PagerState,
onNextStep: () -> Unit,
onNavigateToHome: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
HorizontalPager(
Expand Down Expand Up @@ -53,6 +54,7 @@ fun FortunePager(
5 -> FortuneCompletePage(
hasReward = state.hasReward,
onCompleteClick = onNextStep,
onNavigateToHome = onNavigateToHome,
)
else -> {}
}
Expand Down