Reworked logic - show notification only if is timer running iin the background

This commit is contained in:
Pavol Franek 2020-03-10 15:20:02 +01:00
parent f6d9c5aece
commit 060b8c6a23
7 changed files with 257 additions and 176 deletions

View file

@ -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'
}

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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))
}

View file

@ -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)
}
}

View file

@ -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()
}

View file

@ -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()
}