diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ccfd8486..f2896f58 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,9 +69,17 @@ - + + + + diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/PomodoroModule.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/PomodoroModule.kt new file mode 100644 index 00000000..cc6444b3 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/PomodoroModule.kt @@ -0,0 +1,27 @@ +package com.pomonyang.mohanyang.presentation.di + +import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroTimerRepository +import com.pomonyang.mohanyang.presentation.service.PomodoroTimer +import com.pomonyang.mohanyang.presentation.service.focus.FocusTimer +import com.pomonyang.mohanyang.presentation.service.rest.RestTimer +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ServiceComponent + +@Module +@InstallIn(ServiceComponent::class) +internal object PomodoroModule { + + @Provides + @FocusTimerType + fun provideFocusTimer( + timerRepository: PomodoroTimerRepository + ): PomodoroTimer = FocusTimer(timerRepository = timerRepository) + + @Provides + @RestTimerType + fun provideRestTimer( + timerRepository: PomodoroTimerRepository + ): PomodoroTimer = RestTimer(timerRepository = timerRepository) +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/Qualifier.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/Qualifier.kt index 0c408aba..74f9b48d 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/Qualifier.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/di/Qualifier.kt @@ -5,3 +5,11 @@ import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.BINARY) annotation class PomodoroNotification + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class FocusTimerType + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class RestTimerType diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/noti/PomodoroNotificationManager.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/noti/PomodoroNotificationManager.kt new file mode 100644 index 00000000..a988ed87 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/noti/PomodoroNotificationManager.kt @@ -0,0 +1,66 @@ +package com.pomonyang.mohanyang.presentation.noti + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import androidx.core.app.NotificationCompat +import com.pomonyang.mohanyang.presentation.di.PomodoroNotification +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_CHANNEL_ID +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_CHANNEL_NAME +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_ID +import javax.inject.Inject +import timber.log.Timber + +internal class PomodoroNotificationManager @Inject constructor( + @PomodoroNotification private val notificationBuilder: NotificationCompat.Builder, + private val notificationManager: NotificationManager +) { + fun createNotification(isFocus: Boolean): Notification { + Timber.tag("TIMER").d("createNotification > isFocus $isFocus") + val notificationChannelId = POMODORO_NOTIFICATION_CHANNEL_ID + val notificationChannel = NotificationChannel( + notificationChannelId, + POMODORO_NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(notificationChannel) + + return notificationBuilder + .setContentText(if (isFocus) "집중 시간이다냥" else "휴식 시간이다냥") + .build().apply { + flags = Notification.FLAG_NO_CLEAR + } + } + + fun notifyFocusEnd() { + notificationManager.notify( + POMODORO_NOTIFICATION_ID, + notificationBuilder.setContentText("집중 시간이 끝났습니다!").build() + ) + } + + fun notifyFocusExceed() { + notificationManager.notify( + POMODORO_NOTIFICATION_ID, + notificationBuilder.setContentText( + "너무 오랫동안 자리를 비웠다냥" + ).build() + ) + } + + fun notifyRestEnd() { + notificationManager.notify( + POMODORO_NOTIFICATION_ID, + notificationBuilder.setContentText("휴식 시간이 끝났습니다!").build() + ) + } + + fun notifyRestExceed() { + notificationManager.notify( + POMODORO_NOTIFICATION_ID, + notificationBuilder.setContentText( + "너무 오랫동안 자리를 비웠다냥" + ).build() + ) + } +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/setting/PomodoroSettingScreen.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/setting/PomodoroSettingScreen.kt index 4a27144b..81a91d01 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/setting/PomodoroSettingScreen.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/home/setting/PomodoroSettingScreen.kt @@ -62,7 +62,8 @@ import com.pomonyang.mohanyang.presentation.theme.MnTheme import com.pomonyang.mohanyang.presentation.util.MnNotificationManager import com.pomonyang.mohanyang.presentation.util.clickableSingle import com.pomonyang.mohanyang.presentation.util.collectWithLifecycle -import com.pomonyang.mohanyang.presentation.util.stopTimer +import com.pomonyang.mohanyang.presentation.util.stopFocusTimer +import com.pomonyang.mohanyang.presentation.util.stopRestTimer import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -113,8 +114,8 @@ fun PomodoroSettingRoute( } private fun stopAllNotification(context: Context) { - context.stopTimer(false) - context.stopTimer(true) + context.stopFocusTimer() + context.stopRestTimer() MnNotificationManager.stopInterrupt(context) } diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/focus/PomodoroFocusScreen.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/focus/PomodoroFocusScreen.kt index 9b1e28d3..01ab7198 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/focus/PomodoroFocusScreen.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/focus/PomodoroFocusScreen.kt @@ -44,8 +44,8 @@ import com.pomonyang.mohanyang.presentation.util.DevicePreviews import com.pomonyang.mohanyang.presentation.util.MnNotificationManager.startInterrupt import com.pomonyang.mohanyang.presentation.util.MnNotificationManager.stopInterrupt import com.pomonyang.mohanyang.presentation.util.collectWithLifecycle -import com.pomonyang.mohanyang.presentation.util.startTimer -import com.pomonyang.mohanyang.presentation.util.stopTimer +import com.pomonyang.mohanyang.presentation.util.startFocusTimer +import com.pomonyang.mohanyang.presentation.util.stopFocusTimer @Composable fun PomodoroFocusRoute( @@ -98,7 +98,7 @@ fun PomodoroFocusRoute( LaunchedEffect(state.maxFocusTime) { if (state.maxFocusTime != 0) { - context.startTimer(true, state.maxFocusTime) + context.startFocusTimer(state.maxFocusTime) } } @@ -125,7 +125,7 @@ fun PomodoroFocusRoute( private fun stopNotification(context: Context) { stopInterrupt(context) - context.stopTimer(true) + context.stopFocusTimer() } @Composable diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/rest/PomodoroRestScreen.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/rest/PomodoroRestScreen.kt index 52216d56..fae897d2 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/rest/PomodoroRestScreen.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/screen/pomodoro/rest/PomodoroRestScreen.kt @@ -40,8 +40,8 @@ import com.pomonyang.mohanyang.presentation.screen.pomodoro.PomodoroTimerViewMod import com.pomonyang.mohanyang.presentation.theme.MnTheme import com.pomonyang.mohanyang.presentation.util.DevicePreviews import com.pomonyang.mohanyang.presentation.util.collectWithLifecycle -import com.pomonyang.mohanyang.presentation.util.startTimer -import com.pomonyang.mohanyang.presentation.util.stopTimer +import com.pomonyang.mohanyang.presentation.util.startRestTimer +import com.pomonyang.mohanyang.presentation.util.stopRestTimer @Composable fun PomodoroRestRoute( @@ -59,12 +59,12 @@ fun PomodoroRestRoute( when (effect) { is PomodoroRestEffect.ShowSnackbar -> onShowSnackbar(effect.message, effect.iconRes) PomodoroRestEffect.GoToHome -> { - context.stopTimer(false) + context.stopRestTimer() goToHome() } PomodoroRestEffect.GoToPomodoroFocus -> { - context.stopTimer(false) + context.stopRestTimer() goToFocus() } } @@ -76,7 +76,7 @@ fun PomodoroRestRoute( LaunchedEffect(timerState.maxRestTime) { if (timerState.maxRestTime != 0) { - context.startTimer(false, timerState.maxRestTime) + context.startRestTimer(timerState.maxRestTime) } } diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimer.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimer.kt new file mode 100644 index 00000000..8be7eefe --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimer.kt @@ -0,0 +1,6 @@ +package com.pomonyang.mohanyang.presentation.service + +internal interface PomodoroTimer { + fun startTimer(maxTime: Int, eventHandler: PomodoroTimerEventHandler) + fun stopTimer() +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerEventHandler.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerEventHandler.kt new file mode 100644 index 00000000..e377c883 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerEventHandler.kt @@ -0,0 +1,6 @@ +package com.pomonyang.mohanyang.presentation.service + +internal interface PomodoroTimerEventHandler { + fun onTimeEnd() + fun onTimeExceeded() +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerServiceExtras.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerServiceExtras.kt similarity index 56% rename from presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerServiceExtras.kt rename to presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerServiceExtras.kt index 96daf58b..3f692d57 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerServiceExtras.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/PomodoroTimerServiceExtras.kt @@ -1,7 +1,6 @@ -package com.pomonyang.mohanyang.presentation.util +package com.pomonyang.mohanyang.presentation.service -object PomodoroTimerServiceExtras { - const val INTENT_TIMER_IS_FOCUS = "mohanyang.intent.TIMER_FOCUS" +internal object PomodoroTimerServiceExtras { const val INTENT_TIMER_MAX_TIME = "mohanyang.intent.MAX_TIME" const val ACTION_TIMER_START = "mohanyang.action.TIMER_START" const val ACTION_TIMER_STOP = "mohanyang.action.TIMER_STOP" diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/FocusTimer.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/FocusTimer.kt new file mode 100644 index 00000000..b19480d5 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/FocusTimer.kt @@ -0,0 +1,52 @@ +package com.pomonyang.mohanyang.presentation.service.focus + +import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroTimerRepository +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.MAX_EXCEEDED_TIME +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.ONE_SECOND +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.TIMER_DELAY +import com.pomonyang.mohanyang.presentation.service.PomodoroTimer +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerEventHandler +import java.util.Timer +import javax.inject.Inject +import kotlin.concurrent.fixedRateTimer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import timber.log.Timber + +internal class FocusTimer @Inject constructor( + private val timerRepository: PomodoroTimerRepository +) : PomodoroTimer { + + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private var timer: Timer? = null + private var timeElapsed = 0 + + override fun startTimer(maxTime: Int, eventHandler: PomodoroTimerEventHandler) { + Timber.tag("TIMER").d("startFocus timer / maxTime : $maxTime") + if (timer == null) { + timeElapsed = 0 + timer = fixedRateTimer(initialDelay = TIMER_DELAY, period = TIMER_DELAY) { + scope.launch { + timeElapsed += ONE_SECOND + timerRepository.incrementFocusedTime() + + Timber.tag("TIMER").d("countFocusTime: $timeElapsed ") + + if (timeElapsed >= maxTime) { + eventHandler.onTimeEnd() + } else if (timeElapsed >= maxTime + MAX_EXCEEDED_TIME) { + eventHandler.onTimeExceeded() + stopTimer() + } + } + } + } + } + + override fun stopTimer() { + timer?.cancel() + timer = null + } +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/PomodoroFocusTimerService.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/PomodoroFocusTimerService.kt new file mode 100644 index 00000000..fea420f0 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/focus/PomodoroFocusTimerService.kt @@ -0,0 +1,64 @@ +package com.pomonyang.mohanyang.presentation.service.focus + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import com.pomonyang.mohanyang.presentation.di.FocusTimerType +import com.pomonyang.mohanyang.presentation.noti.PomodoroNotificationManager +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_ID +import com.pomonyang.mohanyang.presentation.service.PomodoroTimer +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerEventHandler +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerServiceExtras +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +import timber.log.Timber + +@AndroidEntryPoint +internal class PomodoroFocusTimerService : + Service(), + PomodoroTimerEventHandler { + + @FocusTimerType + @Inject + lateinit var focusTimer: PomodoroTimer + + @Inject + lateinit var pomodoroNotificationManager: PomodoroNotificationManager + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val maxTime = intent.getIntExtra(PomodoroTimerServiceExtras.INTENT_TIMER_MAX_TIME, 0) + Timber.tag("TIMER").d("onStartCommand > ${intent.action} / maxTime: $maxTime") + when (intent.action) { + PomodoroTimerServiceExtras.ACTION_TIMER_START -> { + startForeground( + POMODORO_NOTIFICATION_ID, + pomodoroNotificationManager.createNotification(true) + ) + focusTimer.startTimer(maxTime, this) + } + + PomodoroTimerServiceExtras.ACTION_TIMER_STOP -> { + stopForeground(STOP_FOREGROUND_REMOVE) + focusTimer.stopTimer() + } + } + + return START_NOT_STICKY + } + + override fun onTimeEnd() { + pomodoroNotificationManager.notifyFocusEnd() + } + + override fun onTimeExceeded() { + pomodoroNotificationManager.notifyFocusExceed() + } + + override fun stopService(name: Intent?): Boolean { + stopForeground(STOP_FOREGROUND_REMOVE) + focusTimer.stopTimer() + return super.stopService(name) + } +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/PomodoroRestTimerService.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/PomodoroRestTimerService.kt new file mode 100644 index 00000000..9e5ec6d5 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/PomodoroRestTimerService.kt @@ -0,0 +1,62 @@ +package com.pomonyang.mohanyang.presentation.service.rest + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import com.pomonyang.mohanyang.presentation.di.RestTimerType +import com.pomonyang.mohanyang.presentation.noti.PomodoroNotificationManager +import com.pomonyang.mohanyang.presentation.service.PomodoroTimer +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerEventHandler +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerServiceExtras +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +import kotlin.random.Random + +@AndroidEntryPoint +internal class PomodoroRestTimerService : + Service(), + PomodoroTimerEventHandler { + + @RestTimerType + @Inject + lateinit var restTimer: PomodoroTimer + + @Inject + lateinit var pomodoroNotificationManager: PomodoroNotificationManager + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val maxTime = intent.getIntExtra(PomodoroTimerServiceExtras.INTENT_TIMER_MAX_TIME, 0) + when (intent.action) { + PomodoroTimerServiceExtras.ACTION_TIMER_START -> { + startForeground( + Random.nextInt(), + pomodoroNotificationManager.createNotification(false) + ) + restTimer.startTimer(maxTime, this) + } + + PomodoroTimerServiceExtras.ACTION_TIMER_STOP -> { + stopForeground(STOP_FOREGROUND_REMOVE) + restTimer.stopTimer() + } + } + + return START_NOT_STICKY + } + + override fun onTimeEnd() { + pomodoroNotificationManager.notifyRestEnd() + } + + override fun onTimeExceeded() { + pomodoroNotificationManager.notifyRestExceed() + } + + override fun stopService(name: Intent?): Boolean { + stopForeground(STOP_FOREGROUND_REMOVE) + restTimer.stopTimer() + return super.stopService(name) + } +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/RestTimer.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/RestTimer.kt new file mode 100644 index 00000000..c8eaf877 --- /dev/null +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/service/rest/RestTimer.kt @@ -0,0 +1,51 @@ +package com.pomonyang.mohanyang.presentation.service.rest + +import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroTimerRepository +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.MAX_EXCEEDED_TIME +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.ONE_SECOND +import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.TIMER_DELAY +import com.pomonyang.mohanyang.presentation.service.PomodoroTimer +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerEventHandler +import java.util.Timer +import javax.inject.Inject +import kotlin.concurrent.fixedRateTimer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber + +internal class RestTimer @Inject constructor( + private val timerRepository: PomodoroTimerRepository +) : PomodoroTimer { + + private var timer: Timer? = null + private var timeElapsed = 0 + private val scope = CoroutineScope(Dispatchers.IO) + + override fun startTimer(maxTime: Int, eventHandler: PomodoroTimerEventHandler) { + Timber.tag("TIMER").d("startRest timer / maxTime : $maxTime") + if (timer == null) { + timeElapsed = 0 + timer = fixedRateTimer(initialDelay = TIMER_DELAY, period = TIMER_DELAY) { + scope.launch { + timeElapsed += ONE_SECOND + timerRepository.incrementRestedTime() + + Timber.tag("TIMER").d("countRestTime: $timeElapsed ") + + if (timeElapsed >= maxTime) { + eventHandler.onTimeEnd() + } else if (timeElapsed >= maxTime + MAX_EXCEEDED_TIME) { + eventHandler.onTimeExceeded() + stopTimer() + } + } + } + } + } + + override fun stopTimer() { + timer?.cancel() + timer = null + } +} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerService.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerService.kt deleted file mode 100644 index 08847799..00000000 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/PomodoroTimerService.kt +++ /dev/null @@ -1,177 +0,0 @@ -package com.pomonyang.mohanyang.presentation.util - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.Service -import android.content.Intent -import android.os.IBinder -import androidx.core.app.NotificationCompat -import com.pomonyang.mohanyang.data.repository.pomodoro.PomodoroTimerRepository -import com.pomonyang.mohanyang.presentation.di.PomodoroNotification -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.MAX_EXCEEDED_TIME -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.ONE_SECOND -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_CHANNEL_ID -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_CHANNEL_NAME -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.POMODORO_NOTIFICATION_ID -import com.pomonyang.mohanyang.presentation.screen.PomodoroConstants.TIMER_DELAY -import com.pomonyang.mohanyang.presentation.util.MnNotificationManager.notifyFocusEnd -import com.pomonyang.mohanyang.presentation.util.MnNotificationManager.notifyRestEnd -import dagger.hilt.android.AndroidEntryPoint -import java.util.Timer -import javax.inject.Inject -import kotlin.concurrent.fixedRateTimer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import timber.log.Timber - -@AndroidEntryPoint -class PomodoroTimerService : Service() { - - @Inject - lateinit var pomodoroTimerRepository: PomodoroTimerRepository - - @PomodoroNotification - @Inject - lateinit var notificationBuilder: NotificationCompat.Builder - - @Inject - lateinit var notificationManager: NotificationManager - - private var focusTimer: Timer? = null - private var restTimer: Timer? = null - - private var scope = CoroutineScope(Dispatchers.IO) - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - val isFocus = intent.getBooleanExtra(PomodoroTimerServiceExtras.INTENT_TIMER_IS_FOCUS, true) - val maxTime = intent.getIntExtra(PomodoroTimerServiceExtras.INTENT_TIMER_MAX_TIME, 0) - val action = intent.action - - Timber.tag(TAG).d("isFocus $isFocus / maxTime $maxTime / $action") - - when (action) { - PomodoroTimerServiceExtras.ACTION_TIMER_START -> { - startForeground(POMODORO_NOTIFICATION_ID, createNotification(isFocus)) - if (isFocus) { - startFocusTimer(maxTime) - } else { - startRestTimer(maxTime) - } - } - - PomodoroTimerServiceExtras.ACTION_TIMER_STOP -> { - stopForeground(STOP_FOREGROUND_REMOVE) - if (isFocus) { - stopFocusTimer() - } else { - stopRestTimer() - } - } - } - - return START_NOT_STICKY - } - - override fun stopService(name: Intent?): Boolean { - Timber.tag(TAG).d("stopService") - stopForeground(STOP_FOREGROUND_REMOVE) - stopFocusTimer() - stopRestTimer() - return super.stopService(name) - } - - private fun startFocusTimer(maxTime: Int) { - if (focusTimer == null) { - var focusTimeElapsed = 0 - Timber.tag(TAG).d("Focus 타이머 시작 ${this@PomodoroTimerService.hashCode()}") - focusTimer = fixedRateTimer(initialDelay = TIMER_DELAY, period = TIMER_DELAY) { - scope.launch { - focusTimeElapsed += ONE_SECOND - - if (focusTimeElapsed == maxTime) { - Timber.tag(TAG).d("Focus 타이머가 maxTime $maxTime 에 도달하여 집중 끝 알림 발송") - notifyFocusEnd(this@PomodoroTimerService) - } - - if (focusTimeElapsed > maxTime + MAX_EXCEEDED_TIME) { - Timber.tag(TAG).d("Focus 타이머가 최대 머무를 수 있는 ${maxTime + MAX_EXCEEDED_TIME} 에 도달하여 중지됨") - notificationManager.notify( - POMODORO_NOTIFICATION_ID, - notificationBuilder.setContentText( - "너무 오랫동안 자리를 비웠다냥" - ).build() - ) - stopFocusTimer() - } else { - Timber.tag(TAG).d("Focus 타이머 작동 중 / 경과 시간: $focusTimeElapsed ${this@PomodoroTimerService.hashCode()}") - pomodoroTimerRepository.incrementFocusedTime() - } - } - } - } - } - - private fun startRestTimer(maxTime: Int) { - if (restTimer == null) { - var restTimeElapsed = 0 - Timber.tag(TAG).d("Rest 타이머 시작 ${this@PomodoroTimerService.hashCode()}") - restTimer = fixedRateTimer(initialDelay = TIMER_DELAY, period = TIMER_DELAY) { - scope.launch { - restTimeElapsed += ONE_SECOND - - if (restTimeElapsed == maxTime) { - Timber.tag(TAG).d("Rest 타이머가 maxTime $maxTime 에 도달하여 휴식 끝 알림 발송") - notifyRestEnd(this@PomodoroTimerService) - } - - if (restTimeElapsed > maxTime + MAX_EXCEEDED_TIME) { - Timber.tag(TAG).d("Rest 타이머가 최대 머무를 수 있는 ${maxTime + MAX_EXCEEDED_TIME} 에 도달하여 중지됨") - stopRestTimer() - } else { - Timber.tag(TAG).d("Rest 타이머 작동 중 / 경과 시간: $restTimeElapsed ${this@PomodoroTimerService.hashCode()}") - pomodoroTimerRepository.incrementRestedTime() - } - } - } - } - } - - private fun stopFocusTimer() { - focusTimer?.cancel() - focusTimer = null - Timber.tag(TAG).d("Focus 타이머 중지 ${this@PomodoroTimerService.hashCode()}") - } - - private fun stopRestTimer() { - restTimer?.cancel() - restTimer = null - Timber.tag(TAG).d("Rest 타이머 중지 ${this@PomodoroTimerService.hashCode()}") - } - - private fun createNotification(isFocus: Boolean): Notification { - val notificationChannelId = POMODORO_NOTIFICATION_CHANNEL_ID - val notificationChannel = NotificationChannel( - /* id = */ - notificationChannelId, - /* name = */ - POMODORO_NOTIFICATION_CHANNEL_NAME, - /* importance = */ - NotificationManager.IMPORTANCE_DEFAULT - ) - notificationManager.createNotificationChannel(notificationChannel) - - return notificationBuilder - .setContentText(if (isFocus) "집중 시간이다냥" else "휴식 시간이다냥") - .build().apply { - flags = Notification.FLAG_NO_CLEAR - } - } - - companion object { - private const val TAG = "PomodoroTimerService" - } -} diff --git a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/TimerUtils.kt b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/TimerUtils.kt index 8c25f9d3..39a34d3d 100644 --- a/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/TimerUtils.kt +++ b/presentation/src/main/java/com/pomonyang/mohanyang/presentation/util/TimerUtils.kt @@ -3,14 +3,18 @@ package com.pomonyang.mohanyang.presentation.util import android.content.Context import android.content.Intent import androidx.core.os.bundleOf +import com.pomonyang.mohanyang.presentation.service.PomodoroTimerServiceExtras +import com.pomonyang.mohanyang.presentation.service.focus.PomodoroFocusTimerService +import com.pomonyang.mohanyang.presentation.service.rest.PomodoroRestTimerService +import timber.log.Timber -fun Context.startTimer(isFocus: Boolean, maxTime: Int) { +internal fun Context.startFocusTimer(maxTime: Int) { + Timber.tag("TIMER").d("startFocusTimer") startService( - Intent(this, PomodoroTimerService::class.java).apply { + Intent(this, PomodoroFocusTimerService::class.java).apply { action = PomodoroTimerServiceExtras.ACTION_TIMER_START putExtras( bundleOf( - PomodoroTimerServiceExtras.INTENT_TIMER_IS_FOCUS to isFocus, PomodoroTimerServiceExtras.INTENT_TIMER_MAX_TIME to maxTime ) ) @@ -18,11 +22,34 @@ fun Context.startTimer(isFocus: Boolean, maxTime: Int) { ) } -fun Context.stopTimer(isFocus: Boolean) { +internal fun Context.startRestTimer(maxTime: Int) { + Timber.tag("TIMER").d("startRestTimer") startService( - Intent(this, PomodoroTimerService::class.java).apply { + Intent(this, PomodoroRestTimerService::class.java).apply { + action = PomodoroTimerServiceExtras.ACTION_TIMER_START + putExtras( + bundleOf( + PomodoroTimerServiceExtras.INTENT_TIMER_MAX_TIME to maxTime + ) + ) + } + ) +} + +internal fun Context.stopFocusTimer() { + Timber.tag("TIMER").d("stopFocusTimer") + startService( + Intent(this, PomodoroFocusTimerService::class.java).apply { + action = PomodoroTimerServiceExtras.ACTION_TIMER_STOP + } + ) +} + +internal fun Context.stopRestTimer() { + Timber.tag("TIMER").d("stopRestTimer") + startService( + Intent(this, PomodoroRestTimerService::class.java).apply { action = PomodoroTimerServiceExtras.ACTION_TIMER_STOP - putExtra(PomodoroTimerServiceExtras.INTENT_TIMER_IS_FOCUS, isFocus) } ) }