Skip to content

Commit

Permalink
Merge pull request #140 from Nexters/feature/service-refactor
Browse files Browse the repository at this point in the history
Feature: Timer Service Refactoring
  • Loading branch information
lee-ji-hoon authored Oct 23, 2024
2 parents ea3a6c7 + a46386e commit 22ce317
Show file tree
Hide file tree
Showing 16 changed files with 400 additions and 200 deletions.
12 changes: 10 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@
</service>

<service
android:name=".presentation.util.PomodoroTimerService"
android:name=".presentation.service.focus.PomodoroFocusTimerService"
android:foregroundServiceType="specialUse">
<meta-data
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="timer_running_in_background" />
</service>

<service
android:name=".presentation.service.rest.PomodoroRestTimerService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="timer_running_in_background" />
</service>
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -113,8 +114,8 @@ fun PomodoroSettingRoute(
}

private fun stopAllNotification(context: Context) {
context.stopTimer(false)
context.stopTimer(true)
context.stopFocusTimer()
context.stopRestTimer()
MnNotificationManager.stopInterrupt(context)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -98,7 +98,7 @@ fun PomodoroFocusRoute(

LaunchedEffect(state.maxFocusTime) {
if (state.maxFocusTime != 0) {
context.startTimer(true, state.maxFocusTime)
context.startFocusTimer(state.maxFocusTime)
}
}

Expand All @@ -125,7 +125,7 @@ fun PomodoroFocusRoute(

private fun stopNotification(context: Context) {
stopInterrupt(context)
context.stopTimer(true)
context.stopFocusTimer()
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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()
}
}
Expand All @@ -76,7 +76,7 @@ fun PomodoroRestRoute(

LaunchedEffect(timerState.maxRestTime) {
if (timerState.maxRestTime != 0) {
context.startTimer(false, timerState.maxRestTime)
context.startRestTimer(timerState.maxRestTime)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pomonyang.mohanyang.presentation.service

internal interface PomodoroTimer {
fun startTimer(maxTime: Int, eventHandler: PomodoroTimerEventHandler)
fun stopTimer()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pomonyang.mohanyang.presentation.service

internal interface PomodoroTimerEventHandler {
fun onTimeEnd()
fun onTimeExceeded()
}
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 22ce317

Please sign in to comment.