Reworked logic - show notification only if is timer running iin the background
This commit is contained in:
parent
f6d9c5aece
commit
060b8c6a23
7 changed files with 257 additions and 176 deletions
|
@ -76,4 +76,5 @@ dependencies {
|
|||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
|
||||
implementation 'org.greenrobot:eventbus:3.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
}
|
||||
|
|
|
@ -1,16 +1,110 @@
|
|||
package com.simplemobiletools.clock
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.simplemobiletools.clock.extensions.config
|
||||
import com.simplemobiletools.clock.extensions.getOpenTimerTabIntent
|
||||
import com.simplemobiletools.clock.extensions.getTimerNotification
|
||||
import com.simplemobiletools.clock.helpers.TIMER_NOTIF_ID
|
||||
import com.simplemobiletools.clock.services.TimerState
|
||||
import com.simplemobiletools.clock.services.TimerStopService
|
||||
import com.simplemobiletools.clock.services.startTimerService
|
||||
import com.simplemobiletools.commons.extensions.checkUseEnglish
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class App : Application(), LifecycleObserver {
|
||||
|
||||
private var timer: CountDownTimer? = null
|
||||
private var lastTick = 0L
|
||||
|
||||
class App : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Stetho.initializeWithDefaults(this)
|
||||
}
|
||||
|
||||
checkUseEnglish()
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
private fun onAppBackgrounded() {
|
||||
if (config.timerState is TimerState.Running) {
|
||||
startTimerService(this)
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
private fun onAppForegrounded() {
|
||||
EventBus.getDefault().post(TimerStopService)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Idle) {
|
||||
config.timerState = state
|
||||
timer?.cancel()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Start) {
|
||||
timer = object : CountDownTimer(state.duration, 1000) {
|
||||
override fun onTick(tick: Long) {
|
||||
lastTick = tick
|
||||
|
||||
val newState = TimerState.Running(state.duration, tick)
|
||||
EventBus.getDefault().post(newState)
|
||||
config.timerState = newState
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
EventBus.getDefault().post(TimerState.Finish(state.duration))
|
||||
EventBus.getDefault().post(TimerStopService)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: TimerState.Finish) {
|
||||
val pendingIntent = getOpenTimerTabIntent()
|
||||
val notification = getTimerNotification(pendingIntent, false) //MAYBE IN FUTURE ADD TIME TO NOTIFICATION
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(TIMER_NOTIF_ID, notification)
|
||||
|
||||
EventBus.getDefault().post(TimerState.Finished)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Finished) {
|
||||
config.timerState = state
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: TimerState.Pause) {
|
||||
EventBus.getDefault().post(TimerState.Paused(event.duration, lastTick))
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Paused) {
|
||||
config.timerState = state
|
||||
timer?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ val timerStates = valueOf<TimerState>()
|
|||
.registerSubtype(TimerState.Pause::class.java)
|
||||
.registerSubtype(TimerState.Paused::class.java)
|
||||
.registerSubtype(TimerState.Finish::class.java)
|
||||
.registerSubtype(TimerState.Finished::class.java)
|
||||
|
||||
inline fun <reified T : Any> valueOf(): RuntimeTypeAdapterFactory<T> = RuntimeTypeAdapterFactory.of(T::class.java)
|
||||
|
|
@ -13,7 +13,6 @@ import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog
|
|||
import com.simplemobiletools.clock.extensions.*
|
||||
import com.simplemobiletools.clock.helpers.PICK_AUDIO_FILE_INTENT_ID
|
||||
import com.simplemobiletools.clock.services.TimerState
|
||||
import com.simplemobiletools.clock.services.startTimerService
|
||||
import com.simplemobiletools.commons.dialogs.SelectAlarmSoundDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ALARM_SOUND_TYPE_ALARM
|
||||
|
@ -22,6 +21,7 @@ import kotlinx.android.synthetic.main.fragment_timer.view.*
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class TimerFragment : Fragment() {
|
||||
|
||||
|
@ -62,7 +62,27 @@ class TimerFragment : Fragment() {
|
|||
}
|
||||
|
||||
timer_play_pause.setOnClickListener {
|
||||
context.startTimerService()
|
||||
val state = config.timerState
|
||||
|
||||
when (state) {
|
||||
is TimerState.Idle -> {
|
||||
EventBus.getDefault().post(TimerState.Start(config.timerSeconds.secondsToMillis))
|
||||
}
|
||||
|
||||
is TimerState.Paused -> {
|
||||
EventBus.getDefault().post(TimerState.Start(state.tick))
|
||||
}
|
||||
|
||||
is TimerState.Running -> {
|
||||
EventBus.getDefault().post(TimerState.Pause(state.tick))
|
||||
}
|
||||
|
||||
is TimerState.Finished -> {
|
||||
EventBus.getDefault().post(TimerState.Start(config.timerSeconds.secondsToMillis))
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
timer_reset.setOnClickListener {
|
||||
|
@ -118,7 +138,7 @@ class TimerFragment : Fragment() {
|
|||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Running) {
|
||||
view.timer_time.text = state.tick.div(1000).toInt().getFormattedDuration()
|
||||
view.timer_time.text = state.tick.div(1000F).roundToInt().getFormattedDuration()
|
||||
updateViewStates(state)
|
||||
}
|
||||
|
||||
|
@ -127,22 +147,28 @@ class TimerFragment : Fragment() {
|
|||
updateViewStates(state)
|
||||
}
|
||||
|
||||
private fun updateViewStates(timerState: TimerState) {
|
||||
view.timer_reset.beVisibleIf(timerState is TimerState.Running || timerState is TimerState.Paused)
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Finished) {
|
||||
view.timer_time.text = 0.getFormattedDuration()
|
||||
updateViewStates(state)
|
||||
}
|
||||
|
||||
val drawableId =
|
||||
if (timerState is TimerState.Running) {
|
||||
R.drawable.ic_pause_vector
|
||||
} else {
|
||||
R.drawable.ic_play_vector
|
||||
}
|
||||
private fun updateViewStates(state: TimerState) {
|
||||
|
||||
val iconColor =
|
||||
if (requiredActivity.getAdjustedPrimaryColor() == Color.WHITE) {
|
||||
Color.BLACK
|
||||
} else {
|
||||
requiredActivity.config.textColor
|
||||
}
|
||||
val resetPossible = state is TimerState.Running || state is TimerState.Paused || state is TimerState.Finished
|
||||
view.timer_reset.beVisibleIf(resetPossible)
|
||||
|
||||
val drawableId = if (state is TimerState.Running) {
|
||||
R.drawable.ic_pause_vector
|
||||
} else {
|
||||
R.drawable.ic_play_vector
|
||||
}
|
||||
|
||||
val iconColor = if (requiredActivity.getAdjustedPrimaryColor() == Color.WHITE) {
|
||||
Color.BLACK
|
||||
} else {
|
||||
requiredActivity.config.textColor
|
||||
}
|
||||
|
||||
view.timer_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.simplemobiletools.clock.extensions.hideTimerNotification
|
||||
import com.simplemobiletools.clock.services.TimerState
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class HideTimerReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
context.hideTimerNotification()
|
||||
EventBus.getDefault().post(TimerState.Idle)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package com.simplemobiletools.clock.services
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.extensions.config
|
||||
import com.simplemobiletools.clock.extensions.getOpenTimerTabIntent
|
||||
import com.simplemobiletools.clock.helpers.TIMER_RUNNING_NOTIF_ID
|
||||
import com.simplemobiletools.commons.extensions.getFormattedDuration
|
||||
import com.simplemobiletools.commons.helpers.isOreoPlus
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun startTimerService(context: Context) {
|
||||
if (isOreoPlus()) {
|
||||
context.startForegroundService(Intent(context, TimerService::class.java))
|
||||
} else {
|
||||
context.startService(Intent(context, TimerService::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
class TimerService : Service() {
|
||||
|
||||
private val bus = EventBus.getDefault()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
bus.register(this)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val formattedDuration = config.timerSeconds.getFormattedDuration()
|
||||
startForeground(TIMER_RUNNING_NOTIF_ID, notification(formattedDuration))
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: TimerStopService) {
|
||||
stopService()
|
||||
}
|
||||
|
||||
private fun stopService() {
|
||||
if (isOreoPlus()) {
|
||||
stopForeground(true)
|
||||
} else {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus.unregister(this)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private fun notification(formattedDuration: String): Notification {
|
||||
val channelId = "simple_alarm_timer"
|
||||
val label = getString(R.string.timer)
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (isOreoPlus()) {
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
NotificationChannel(channelId, label, importance).apply {
|
||||
setSound(null, null)
|
||||
notificationManager.createNotificationChannel(this)
|
||||
}
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this)
|
||||
.setContentTitle(label)
|
||||
.setContentText(formattedDuration)
|
||||
.setSmallIcon(R.drawable.ic_timer)
|
||||
.setContentIntent(this.getOpenTimerTabIntent())
|
||||
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setChannelId(channelId)
|
||||
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
data class StateWrapper(val state: TimerState)
|
||||
|
||||
object TimerStopService
|
||||
|
||||
sealed class TimerState {
|
||||
object Idle : TimerState()
|
||||
data class Start(val duration: Long) : TimerState()
|
||||
data class Running(val duration: Long, val tick: Long) : TimerState()
|
||||
data class Pause(val duration: Long) : TimerState()
|
||||
data class Paused(val duration: Long, val tick: Long) : TimerState()
|
||||
data class Finish(val duration: Long) : TimerState()
|
||||
object Finished : TimerState()
|
||||
}
|
||||
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
package com.simplemobiletools.clock.services
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.extensions.config
|
||||
import com.simplemobiletools.clock.extensions.getOpenTimerTabIntent
|
||||
import com.simplemobiletools.clock.extensions.getTimerNotification
|
||||
import com.simplemobiletools.clock.extensions.secondsToMillis
|
||||
import com.simplemobiletools.clock.helpers.TIMER_NOTIF_ID
|
||||
import com.simplemobiletools.clock.helpers.TIMER_RUNNING_NOTIF_ID
|
||||
import com.simplemobiletools.commons.extensions.getFormattedDuration
|
||||
import com.simplemobiletools.commons.helpers.isOreoPlus
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
fun Context.startTimerService() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(Intent(this, TimerService::class.java))
|
||||
} else {
|
||||
startService(Intent(this, TimerService::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
class TimerService : Service() {
|
||||
|
||||
private var timer: CountDownTimer? = null
|
||||
private var lastTick = 0L
|
||||
private val bus = EventBus.getDefault()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
bus.register(this)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val formattedDuration = config.timerSeconds.getFormattedDuration()
|
||||
startForeground(TIMER_RUNNING_NOTIF_ID, notification(formattedDuration))
|
||||
|
||||
when (val state = config.timerState) {
|
||||
is TimerState.Idle -> bus.post(TimerState.Start(config.timerSeconds.secondsToMillis))
|
||||
is TimerState.Paused -> bus.post(TimerState.Start(state.tick))
|
||||
is TimerState.Running -> bus.post(TimerState.Pause(state.tick))
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Idle) {
|
||||
config.timerState = state
|
||||
timer?.cancel()
|
||||
stopService()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Start) {
|
||||
timer = object : CountDownTimer(state.duration, 1000) {
|
||||
override fun onTick(tick: Long) {
|
||||
lastTick = tick
|
||||
|
||||
val newState = TimerState.Running(state.duration, tick)
|
||||
bus.post(newState)
|
||||
config.timerState = newState
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
bus.post(TimerState.Finish(state.duration))
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: TimerState.Finish) {
|
||||
val pendingIntent = getOpenTimerTabIntent()
|
||||
val notification = getTimerNotification(pendingIntent, false) //MAYBE IN FUTURE ADD TIME TO NOTIFICATION
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(TIMER_NOTIF_ID, notification)
|
||||
|
||||
bus.post(TimerState.Idle)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: TimerState.Pause) {
|
||||
bus.post(TimerState.Paused(event.duration, lastTick))
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(state: TimerState.Paused) {
|
||||
config.timerState = state
|
||||
timer?.cancel()
|
||||
stopService()
|
||||
}
|
||||
|
||||
private fun stopService() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) stopForeground(true)
|
||||
else stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus.unregister(this)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private fun notification(formattedDuration: String): Notification {
|
||||
val channelId = "simple_alarm_timer"
|
||||
val label = getString(R.string.timer)
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (isOreoPlus()) {
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
NotificationChannel(channelId, label, importance).apply {
|
||||
setSound(null, null)
|
||||
notificationManager.createNotificationChannel(this)
|
||||
}
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this)
|
||||
.setContentTitle(label)
|
||||
.setContentText(formattedDuration)
|
||||
.setSmallIcon(R.drawable.ic_timer)
|
||||
.setContentIntent(this.getOpenTimerTabIntent())
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setChannelId(channelId)
|
||||
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
data class StateWrapper(val state: TimerState)
|
||||
|
||||
sealed class TimerState {
|
||||
object Idle: TimerState()
|
||||
data class Start(val duration: Long): TimerState()
|
||||
data class Running(val duration: Long, val tick: Long): TimerState()
|
||||
data class Pause(val duration: Long): TimerState()
|
||||
data class Paused(val duration: Long, val tick: Long): TimerState()
|
||||
data class Finish(val duration: Long): TimerState()
|
||||
}
|
Loading…
Reference in a new issue