Skip to content
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
defaultConfig {
versionCode = 5
versionName = "1.0.3"
targetSdk = 34
targetSdk = 35
}

buildTypes {
Expand Down
12 changes: 1 addition & 11 deletions app/src/main/java/com/yapp/orbit/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.CompositionLocalProvider
Expand All @@ -25,16 +24,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.light(
android.graphics.Color.TRANSPARENT,
android.graphics.Color.TRANSPARENT,
),
navigationBarStyle = SystemBarStyle.light(
android.graphics.Color.BLACK,
android.graphics.Color.BLACK,
),
)
enableEdgeToEdge()
setContent {
val navigator = rememberOrbitNavigator()

Expand Down
89 changes: 54 additions & 35 deletions app/src/main/java/com/yapp/orbit/OrbitNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.navigation.compose.NavHost
import com.yapp.common.navigation.OrbitNavigator
import com.yapp.common.navigation.rememberOrbitNavigator
Expand All @@ -41,55 +50,65 @@ internal fun OrbitNavHost(
) {
val snackBarHostState = remember { SnackbarHostState() }

OrbitBottomSheetLayout(
sheetState = bottomSheetState,
) {
Scaffold(
modifier = modifier,
snackbarHost = {
OrbitSnackBarHost(snackBarHostState = snackBarHostState)
},
containerColor = OrbitTheme.colors.gray_900,
) {
NavHost(
navController = navigator.navController,
startDestination = navigator.startDestination,
modifier = Modifier.navigationBarsPadding(),
Box {
OrbitBottomSheetLayout(sheetState = bottomSheetState) {
Scaffold(
modifier = modifier,
snackbarHost = { OrbitSnackBarHost(snackBarHostState) },
containerColor = OrbitTheme.colors.gray_900,
) {
splashScreen(
navigator = navigator,
)
onboardingNavGraph(
navigator = navigator,
bottomSheetState = bottomSheetState,
)
homeNavGraph(
OrbitNavigationGraph(
navigator = navigator,
bottomSheetState = bottomSheetState,
snackBarHostState = snackBarHostState,
)
missionScreen(navigator = navigator)
fortuneNavGraph(
navigator = navigator,
snackBarHostState = snackBarHostState,
)
settingNavGraph(navigator = navigator)
webViewScreen(navigator = navigator)
}
}

NavigationBarScrim()
}
}

@Composable
private fun OrbitNavigationGraph(
navigator: OrbitNavigator,
bottomSheetState: OrbitBottomSheetState,
snackBarHostState: SnackbarHostState,
) {
NavHost(
navController = navigator.navController,
startDestination = navigator.startDestination,
) {
splashScreen(navigator)
onboardingNavGraph(navigator, bottomSheetState)
homeNavGraph(navigator, bottomSheetState, snackBarHostState)
missionScreen(navigator)
fortuneNavGraph(navigator, snackBarHostState)
settingNavGraph(navigator)
webViewScreen(navigator)
}
}

@Composable
private fun BoxScope.NavigationBarScrim() {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.windowInsetsBottomHeight(WindowInsets.navigationBars)
.background(Color.Black)
.zIndex(1f),
)
}

@Composable
private fun OrbitSnackBarHost(
snackBarHostState: SnackbarHostState,
) {
AnimatedVisibility(
visible = snackBarHostState.currentSnackbarData != null,
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(
targetOffsetY = { it },
) + fadeOut(),
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
) {
SnackbarHost(
hostState = snackBarHostState,
Expand All @@ -102,9 +121,9 @@ private fun OrbitSnackBarHost(
end = 20.dp,
bottom = visuals?.bottomPadding ?: 12.dp,
),
label = visuals?.actionLabel ?: "",
label = visuals?.actionLabel.orEmpty(),
iconRes = visuals?.iconRes,
message = visuals?.message ?: "",
message = visuals?.message.orEmpty(),
onAction = { snackBarHostState.currentSnackbarData?.performAction() },
)
},
Expand Down
2 changes: 2 additions & 0 deletions core/alarm/src/main/java/com/yapp/alarm/AlarmConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ object AlarmConstants {
const val ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE = "com.yapp.orbit.ACTION_ALERT_INTERACTION_CLOSE"

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_ALARM = "com.yapp.orbit.EXTRA_ALARM"
const val EXTRA_ALARM_DAY = "com.yapp.orbit.EXTRA_ALARM_DAY"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import com.yapp.alarm.receivers.AlarmReceiver
fun createAlarmDismissPendingIntent(
applicationContext: Context,
pendingIntentId: Long,
missionType: Int,
missionCount: Int,
): PendingIntent {
val alarmDismissIntent = createAlarmDismissIntent(applicationContext, pendingIntentId)
val alarmDismissIntent = createAlarmDismissIntent(applicationContext, pendingIntentId, missionType, missionCount)
return PendingIntent.getBroadcast(
applicationContext,
pendingIntentId.toInt(),
Expand All @@ -25,10 +27,14 @@ fun createAlarmDismissPendingIntent(
fun createAlarmDismissIntent(
context: Context,
notificationId: Long,
missionType: Int,
missionCount: Int,
): Intent {
return Intent(AlarmConstants.ACTION_ALARM_DISMISSED).apply {
setClass(context, AlarmReceiver::class.java)
putExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, notificationId)
putExtra(AlarmConstants.EXTRA_MISSION_TYPE, missionType)
putExtra(AlarmConstants.EXTRA_MISSION_COUNT, missionCount)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.yapp.alarm.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.core.net.toUri
import com.yapp.alarm.AlarmConstants
Expand All @@ -29,14 +30,25 @@ class AlarmInteractionActivityReceiver(private val activity: ComponentActivity)
activity.finish()

if (!isSnoozed) {
val notificationId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
val missionType = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_TYPE, -1)
val missionCount = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_COUNT, -1)

if (notificationId == -1L || missionType == -1 || missionCount == -1) {
Log.e("AlarmInteraction", "필수 값 누락")
return
}

CoroutineScope(Dispatchers.IO).launch {
val fortuneDate = fortuneRepository.fortuneDateFlow.firstOrNull()
val todayDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE)

if (fortuneDate != todayDate) {
context?.let {
val uriString =
"orbitapp://mission?notificationId=$notificationId&missionType=$missionType&missionCount=$missionCount"
val missionIntent =
Intent(Intent.ACTION_VIEW, "orbitapp://mission".toUri()).apply {
Intent(Intent.ACTION_VIEW, uriString.toUri()).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setPackage(context.packageName)
}
Expand Down
108 changes: 59 additions & 49 deletions core/alarm/src/main/java/com/yapp/alarm/receivers/AlarmReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ class AlarmReceiver : BroadcastReceiver() {
val alarmServiceIntent = createAlarmServiceIntent(context, intent)
when (intent.action) {
AlarmConstants.ACTION_ALARM_TRIGGERED -> {
Log.d("AlarmReceiver", "Alarm Triggered")

val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
alarmServiceIntent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
alarmServiceIntent.getParcelableExtra(
AlarmConstants.EXTRA_ALARM,
Alarm::class.java,
)
} else {
@Suppress("DEPRECATION")
alarmServiceIntent.getParcelableExtra(AlarmConstants.EXTRA_ALARM)
Expand All @@ -68,8 +69,6 @@ class AlarmReceiver : BroadcastReceiver() {
}

AlarmConstants.ACTION_ALARM_SNOOZED -> {
Log.d("AlarmReceiver", "Alarm Snoozed")

val alarm: Alarm? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(AlarmConstants.EXTRA_ALARM, Alarm::class.java)
} else {
Expand All @@ -86,44 +85,54 @@ class AlarmReceiver : BroadcastReceiver() {
)
alarm?.let { handleSnooze(context, it) }

Toast.makeText(context, "알람이 ${alarm?.snoozeInterval}분 후 다시 울려요", Toast.LENGTH_SHORT).show()
Toast.makeText(
context,
"알람이 ${alarm?.snoozeInterval}분 후 다시 울려요",
Toast.LENGTH_SHORT,
).show()
}

AlarmConstants.ACTION_ALARM_DISMISSED -> {
Log.d("AlarmReceiver", "Alarm Dismissed")

val alarmId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
if (alarmId != -1L) {
CoroutineScope(Dispatchers.IO).launch {
val alarms = alarmUseCase.getAllAlarms().first().sortedBy { it.isAlarmActive }
val isFirstAlarm = alarms.firstOrNull()?.id == alarmId

analyticsHelper.logEvent(
AnalyticsEvent(
type = "alarm_dismiss",
properties = mapOf(
AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "$alarmId",
AnalyticsEvent.AlarmPropertiesKeys.DISMISS_IS_FIRST_ALARM to isFirstAlarm,
),
),
)
val existingId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull()
if (existingId == null) {
// 첫 번째 알람 해제 기록
fortuneRepository.saveFirstDismissedAlarmId(alarmId)
} else if (existingId != alarmId) {
// 두 번째 알람 해제 감지 - 기존 기록 삭제
fortuneRepository.clearDismissedAlarmId()
}
}
val notificationId = intent.getLongExtra(AlarmConstants.EXTRA_NOTIFICATION_ID, -1L)
val missionType = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_TYPE, -1)
val missionCount = intent.getIntExtra(AlarmConstants.EXTRA_MISSION_COUNT, -1)

androidAlarmScheduler.cancelSnoozedAlarm(alarmId)
} else {
Log.e("AlarmReceiver", "알람 ID 수신 실패")
if (notificationId == -1L) {
Log.e("AlarmReceiver", "notificationId 수신 실패")
return
}
androidAlarmScheduler.cancelSnoozedAlarm(alarmId)

androidAlarmScheduler.cancelSnoozedAlarm(notificationId)
context.stopService(alarmServiceIntent)
sendBroadCastToCloseAlarmInteractionActivity(context)

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

analyticsHelper.logEvent(
AnalyticsEvent(
type = "alarm_dismiss",
properties = mapOf(
AnalyticsEvent.AlarmPropertiesKeys.ALARM_ID to "$notificationId",
AnalyticsEvent.AlarmPropertiesKeys.DISMISS_IS_FIRST_ALARM to isFirstAlarm,
),
),
)

val existingId = fortuneRepository.firstDismissedAlarmIdFlow.firstOrNull()
if (existingId == null) {
fortuneRepository.saveFirstDismissedAlarmId(notificationId)
} else if (existingId != notificationId) {
fortuneRepository.clearDismissedAlarmId()
}
}

Toast.makeText(context, "알람이 해제되었어요", Toast.LENGTH_SHORT).show()
}
Expand Down Expand Up @@ -160,21 +169,22 @@ class AlarmReceiver : BroadcastReceiver() {
id = alarm.id + AlarmConstants.SNOOZE_ID_OFFSET,
)

Log.d(
"AlarmReceiver",
"Scheduling snooze alarm: alarmId=${updatedAlarm.id}, newTime=${updatedAlarm.hour}:${updatedAlarm.minute}, remaining snoozeCount=$newSnoozeCount",
)

context.stopService(Intent(context, AlarmService::class.java))
androidAlarmScheduler.scheduleAlarm(updatedAlarm)
}

private fun sendBroadCastToCloseAlarmInteractionActivity(context: Context) {
Log.d("AlarmReceiver", "Send Broadcast to close Alarm Interaction Activity")
val alarmAlertActivityCloseIntent =
Intent(AlarmConstants.ACTION_ALARM_INTERACTION_ACTIVITY_CLOSE).apply {
putExtra(AlarmConstants.EXTRA_IS_SNOOZED, false)
}
context.sendBroadcast(alarmAlertActivityCloseIntent)
private fun sendBroadCastToCloseAlarmInteractionActivity(
context: Context,
notificationId: Long,
missionType: Int,
missionCount: Int,
) {
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)
}
context.sendBroadcast(intent)
}
}
Loading