handle multiple timer states

- feat: separate timer actions from states by adding a new class TimerEvent to hold actions Reset, Start, Pause, Finish, Refesh that could be performed on a timer.
- feat: handle multiple countdown timers in the App file by creating a map of the timer id to the countdown timer.
- fix: use gson instance from  TypeAdapter in Room's Converters class
- ref: remove scroll view parent from each timer item, a fix for the keyboard obscuring a label will be implemented in a future commit
This commit is contained in:
Paul Akhamiogu 2021-09-01 19:58:38 +01:00
parent 921ca92885
commit 8474e6a800
15 changed files with 266 additions and 232 deletions

View File

@ -5,16 +5,18 @@ import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.os.CountDownTimer
import android.util.Log
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.extensions.timerHelper
import com.simplemobiletools.clock.helpers.TIMER_NOTIF_ID
import com.simplemobiletools.clock.models.TimerEvent
import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.clock.services.TimerStopService
import com.simplemobiletools.clock.services.startTimerService
@ -25,8 +27,7 @@ import org.greenrobot.eventbus.ThreadMode
class App : Application(), LifecycleObserver {
private var timer: CountDownTimer? = null
private var lastTick = 0L
private var timers = mutableMapOf<Long, CountDownTimer>()
override fun onCreate() {
super.onCreate()
@ -48,63 +49,83 @@ class App : Application(), LifecycleObserver {
@RequiresApi(Build.VERSION_CODES.O)
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun onAppBackgrounded() {
if (config.timerState is TimerState.Running) {
startTimerService(this)
timerHelper.getTimers { timers ->
if (timers.any { it.state is TimerState.Running }) {
startTimerService(this)
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private fun onAppForegrounded() {
EventBus.getDefault().post(TimerStopService)
timerHelper.getTimers { timers ->
val runningTimers = timers.filter { it.state is TimerState.Running }
runningTimers.forEach { timer ->
EventBus.getDefault().post(TimerEvent.Start(timer.id!!, (timer.state as TimerState.Running).tick))
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Idle) {
config.timerState = state
timer?.cancel()
fun onMessageEvent(event: TimerEvent.Reset) {
Log.w(TAG, "onMessageEvent: $event")
updateTimerState(event.timerId, TimerState.Idle)
timers[event.timerId]?.cancel()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Start) {
timer = object : CountDownTimer(state.duration, 1000) {
fun onMessageEvent(event: TimerEvent.Start) {
Log.w(TAG, "onMessageEvent: $event")
val countDownTimer = object : CountDownTimer(event.duration, 1000) {
override fun onTick(tick: Long) {
lastTick = tick
val newState = TimerState.Running(state.duration, tick)
EventBus.getDefault().post(newState)
config.timerState = newState
Log.d(TAG, "onMessageEvent--> $tick")
updateTimerState(event.timerId, TimerState.Running(event.duration, tick))
}
override fun onFinish() {
EventBus.getDefault().post(TimerState.Finish(state.duration))
EventBus.getDefault().post(TimerEvent.Finish(event.timerId, event.duration))
EventBus.getDefault().post(TimerStopService)
}
}.start()
timers[event.timerId] = countDownTimer
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: TimerState.Finish) {
val pendingIntent = getOpenTimerTabIntent()
val notification = getTimerNotification(pendingIntent, false)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(TIMER_NOTIF_ID, notification)
EventBus.getDefault().post(TimerState.Finished)
fun onMessageEvent(event: TimerEvent.Finish) {
Log.w(TAG, "onMessageEvent: $event")
timerHelper.getTimer(event.timerId) { timer ->
val pendingIntent = getOpenTimerTabIntent(event.timerId)
val notification = getTimerNotification(timer, pendingIntent, false)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(TIMER_NOTIF_ID, notification)
updateTimerState(event.timerId, TimerState.Finished)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Finished) {
config.timerState = state
fun onMessageEvent(event: TimerEvent.Pause) {
Log.w(TAG, "onMessageEvent: $event")
timerHelper.getTimer(event.timerId) { timer ->
updateTimerState(event.timerId, TimerState.Paused(event.duration, (timer.state as TimerState.Running).tick))
timers[event.timerId]?.cancel()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: TimerState.Pause) {
EventBus.getDefault().post(TimerState.Paused(event.duration, lastTick))
private fun updateTimerState(timerId: Long, state: TimerState) {
timerHelper.getTimer(timerId) { timer ->
val newTimer = timer.copy(state = state)
Log.w(TAG, "updateTimerState: $newTimer")
timerHelper.insertOrUpdateTimer(newTimer) {
EventBus.getDefault().post(TimerEvent.Refresh(timerId))
timerHelper.getTimer(timerId) {
Log.e(TAG, "updated timer $it")
}
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Paused) {
config.timerState = state
timer?.cancel()
companion object {
private const val TAG = "App"
}
}

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.clock.adapters
import android.media.AudioManager
import android.media.RingtoneManager
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -11,7 +12,10 @@ import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.SimpleActivity
import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog
import com.simplemobiletools.clock.extensions.*
import com.simplemobiletools.clock.extensions.checkAlarmsWithDeletedSoundUri
import com.simplemobiletools.clock.extensions.colorLeftDrawable
import com.simplemobiletools.clock.extensions.config
import com.simplemobiletools.clock.extensions.timerHelper
import com.simplemobiletools.clock.helpers.PICK_AUDIO_FILE_INTENT_ID
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerState
@ -20,16 +24,13 @@ import com.simplemobiletools.commons.extensions.getDefaultAlarmSound
import com.simplemobiletools.commons.extensions.getFormattedDuration
import com.simplemobiletools.commons.extensions.onTextChangeListener
import com.simplemobiletools.commons.models.AlarmSound
import kotlin.math.roundToInt
import kotlinx.android.synthetic.main.item_timer.view.*
import org.greenrobot.eventbus.EventBus
class TimerAdapter(
private val activity: SimpleActivity,
private val onRefresh: () -> Unit,
) : ListAdapter<Timer, TimerAdapter.TimerViewHolder>(diffUtil) {
private val config = activity.config
) : ListAdapter<Timer, TimerAdapter.TimerViewHolder>(diffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimerViewHolder {
return TimerViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_timer, parent, false))
@ -45,28 +46,32 @@ class TimerAdapter(
inner class TimerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.timer_label.onTextChangeListener { text ->
Log.w(TAG, "timer_label")
updateTimer(getItemAt(adapterPosition).copy(label = text), false)
}
itemView.post {
val textColor = activity.config.textColor
itemView.timer_initial_time.colorLeftDrawable(textColor)
itemView.timer_vibrate.colorLeftDrawable(textColor)
itemView.timer_sound.colorLeftDrawable(textColor)
}
}
fun bind(timer: Timer) {
itemView.apply {
timer_time.text = timer.seconds.getFormattedDuration()
timer_label.setText(timer.label)
timer_time.text = timer.seconds.getFormattedDuration()
timer_label.setText(timer.label)
val textColor = activity.config.textColor
//only update when different to prevent flickering and unnecessary updates
if (timer_label.text.toString() != timer.label) {
timer_label.setText(timer.label)
}
timer_initial_time.text = timer.seconds.getFormattedDuration()
timer_initial_time.colorLeftDrawable(textColor)
timer_vibrate.isChecked = timer.vibrate
timer_vibrate.colorLeftDrawable(textColor)
timer_sound.text = timer.soundTitle
timer_sound.colorLeftDrawable(textColor)
timer_time.setOnClickListener {
stopTimer(timer)
}
timer_time.setOnClickListener {
changeDuration(timer)
@ -77,12 +82,13 @@ class TimerAdapter(
}
timer_vibrate_holder.setOnClickListener {
Log.w(TAG, "toggle")
timer_vibrate.toggle()
updateTimer(timer.copy(vibrate = timer_vibrate.isChecked), false)
}
timer_sound.setOnClickListener {
SelectAlarmSoundDialog(activity, config.timerSoundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID,
SelectAlarmSoundDialog(activity, timer.soundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID,
RingtoneManager.TYPE_ALARM, true,
onAlarmPicked = { sound ->
if (sound != null) {
@ -99,39 +105,52 @@ class TimerAdapter(
})
}
timer_label.onTextChangeListener { text ->
updateTimer(timer.copy(label = text), false)
when (timer.state) {
is TimerState.Finished -> {
timer_time.text = 0.getFormattedDuration()
}
is TimerState.Idle -> {
timer_time.text = timer.seconds.getFormattedDuration()
}
is TimerState.Paused -> {
timer_time.text = timer.state.tick.div(1000F).roundToInt().getFormattedDuration()
}
is TimerState.Running -> {
timer_time.text = timer.state.tick.div(1000F).roundToInt().getFormattedDuration()
}
}
}
}
}
private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerState.Idle)
activity.hideTimerNotification()
}
private fun changeDuration(timer: Timer) {
MyTimePickerDialogDialog(activity, timer.seconds) { seconds ->
val timerSeconds = if (seconds <= 0) 10 else seconds
Log.w(TAG, "changeDuration")
updateTimer(timer.copy(seconds = timerSeconds))
}
}
fun updateAlarmSound(timer: Timer, alarmSound: AlarmSound) {
Log.w(TAG, "updateAlarmSound: $timer")
updateTimer(timer.copy(soundTitle = alarmSound.title, soundUri = alarmSound.uri))
}
private fun updateTimer(timer: Timer, refresh: Boolean = true) {
Log.w(TAG, "updateTimer: $timer")
activity.timerHelper.insertOrUpdateTimer(timer)
if (refresh) {
onRefresh.invoke()
}
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
private const val TAG = "TimerAdapter"
private val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
override fun areItemsTheSame(oldItem: Timer, newItem: Timer): Boolean {
return oldItem.id == newItem.id
}

View File

@ -11,6 +11,7 @@ import com.simplemobiletools.clock.fragments.TimerFragment
import com.simplemobiletools.clock.helpers.TABS_COUNT
import com.simplemobiletools.clock.helpers.TAB_ALARM
import com.simplemobiletools.clock.helpers.TAB_CLOCK
import com.simplemobiletools.clock.helpers.TAB_TIMER
import com.simplemobiletools.commons.models.AlarmSound
class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
@ -50,6 +51,6 @@ class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
}
fun updateTimerTabAlarmSound(alarmSound: AlarmSound) {
// (fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound)
(fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound)
}
}

View File

@ -25,6 +25,7 @@ import com.simplemobiletools.clock.helpers.*
import com.simplemobiletools.clock.interfaces.TimerDao
import com.simplemobiletools.clock.models.Alarm
import com.simplemobiletools.clock.models.MyTimeZone
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.receivers.AlarmReceiver
import com.simplemobiletools.clock.receivers.DateTimeWidgetUpdateReceiver
import com.simplemobiletools.clock.receivers.HideAlarmReceiver
@ -139,9 +140,10 @@ fun Context.getOpenAlarmTabIntent(): PendingIntent {
return PendingIntent.getActivity(this, OPEN_ALARMS_TAB_INTENT_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun Context.getOpenTimerTabIntent(): PendingIntent {
fun Context.getOpenTimerTabIntent(timerId: Long): PendingIntent {
val intent = getLaunchIntent() ?: Intent(this, SplashActivity::class.java)
intent.putExtra(OPEN_TAB, TAB_TIMER)
intent.putExtra(TIMER_ID, timerId)
return PendingIntent.getActivity(this, TIMER_NOTIF_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
@ -260,8 +262,8 @@ fun Context.showAlarmNotification(alarm: Alarm) {
}
@SuppressLint("NewApi")
fun Context.getTimerNotification(pendingIntent: PendingIntent, addDeleteIntent: Boolean): Notification {
var soundUri = config.timerSoundUri
fun Context.getTimerNotification(timer: Timer, pendingIntent: PendingIntent, addDeleteIntent: Boolean): Notification {
var soundUri = timer.soundUri
if (soundUri == SILENT) {
soundUri = ""
} else {
@ -292,7 +294,7 @@ fun Context.getTimerNotification(pendingIntent: PendingIntent, addDeleteIntent:
lightColor = getAdjustedPrimaryColor()
setSound(Uri.parse(soundUri), audioAttributes)
if (!config.timerVibrate) {
if (!timer.vibrate) {
vibrationPattern = longArrayOf(0L)
}
@ -321,7 +323,7 @@ fun Context.getTimerNotification(pendingIntent: PendingIntent, addDeleteIntent:
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
if (config.timerVibrate) {
if (timer.vibrate) {
val vibrateArray = LongArray(2) { 500 }
builder.setVibrate(vibrateArray)
}

View File

@ -7,11 +7,8 @@ import com.simplemobiletools.clock.models.TimerState
val timerStates = valueOf<TimerState>()
.registerSubtype(TimerState.Idle::class.java)
.registerSubtype(TimerState.Start::class.java)
.registerSubtype(TimerState.Running::class.java)
.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

@ -2,6 +2,7 @@ package com.simplemobiletools.clock.fragments
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -10,13 +11,16 @@ import androidx.viewpager2.widget.ViewPager2
import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.SimpleActivity
import com.simplemobiletools.clock.adapters.TimerAdapter
import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog
import com.simplemobiletools.clock.extensions.config
import com.simplemobiletools.clock.extensions.hideTimerNotification
import com.simplemobiletools.clock.extensions.secondsToMillis
import com.simplemobiletools.clock.extensions.timerHelper
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerEvent
import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.models.AlarmSound
import kotlinx.android.synthetic.main.fragment_timer.timer_view_pager
import kotlinx.android.synthetic.main.fragment_timer.view.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@ -24,7 +28,7 @@ import org.greenrobot.eventbus.ThreadMode
class TimerFragment : Fragment() {
lateinit var view: ViewGroup
private lateinit var view: ViewGroup
private lateinit var timerAdapter: TimerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
@ -42,7 +46,10 @@ class TimerFragment : Fragment() {
timerAdapter = TimerAdapter(requireActivity() as SimpleActivity) {
refreshTimers()
}
timer_view_pager.adapter = timerAdapter
//set empty page transformer to disable item animations
timer_view_pager.setPageTransformer { _, _ -> }
timer_view_pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
updateViews(position)
@ -64,16 +71,13 @@ class TimerFragment : Fragment() {
timer_play_pause.applyColorFilter(if (activity?.getAdjustedPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE)
timer_reset.applyColorFilter(textColor)
timer_play_pause.setOnClickListener {
val timer = timerAdapter.getItemAt(timer_view_pager.currentItem)
when (val state = timer.state) {
// is TimerState.Idle -> EventBus.getDefault().post(TimerState.Start(timer.seconds.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(timer.seconds.secondsToMillis))
else -> {
}
is TimerState.Idle -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
is TimerState.Paused -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, state.tick))
is TimerState.Running -> EventBus.getDefault().post(TimerEvent.Pause(timer.id!!, state.tick))
is TimerState.Finished -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
}
}
@ -95,67 +99,39 @@ class TimerFragment : Fragment() {
}
private fun updateViews(position: Int) {
val timer = timerAdapter.getItemAt(position)
//check if timer is running to update view
activity?.runOnUiThread {
val timer = timerAdapter.getItemAt(position)
updateViewStates(timer.state)
}
}
private fun refreshTimers(scrollToLast: Boolean = false) {
activity?.timerHelper?.getTimers { timers ->
timerAdapter.submitList(timers)
activity?.runOnUiThread {
view.timer_delete.beVisibleIf(timers.size > 1)
Log.d(TAG, "refreshTimers: $timers")
timerAdapter.submitList(timers) {
if (scrollToLast) {
view.timer_view_pager.currentItem = timers.lastIndex
}
updateViews(timer_view_pager.currentItem)
}
}
}
private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerState.Idle)
EventBus.getDefault().post(TimerEvent.Reset(timer.id!!, timer.seconds.secondsToMillis))
activity?.hideTimerNotification()
// view.timer_time.text = activity?.config?.timerSeconds?.getFormattedDuration()
}
private fun changeDuration() {
MyTimePickerDialogDialog(activity as SimpleActivity, requireContext().config.timerSeconds) { seconds ->
val timerSeconds = if (seconds <= 0) 10 else seconds
activity?.config?.timerSeconds = timerSeconds
val duration = timerSeconds.getFormattedDuration()
// view.timer_initial_time.text = duration
// if (view.timer_reset.isGone()) {
// stopTimer()
// }
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Idle) {
// view.timer_time.text = requiredActivity.config.timerSeconds.getFormattedDuration()
updateViewStates(state)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Running) {
// view.timer_time.text = state.tick.div(1000F).roundToInt().getFormattedDuration()
updateViewStates(state)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Paused) {
updateViewStates(state)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Finished) {
// view.timer_time.text = 0.getFormattedDuration()
updateViewStates(state)
fun onMessageEvent(event: TimerEvent.Refresh) {
Log.d(TAG, "onMessageEvent: $event")
refreshTimers()
}
private fun updateViewStates(state: TimerState) {
val resetPossible = state is TimerState.Running || state is TimerState.Paused || state is TimerState.Finished
view.timer_reset.beVisibleIf(resetPossible)
view.timer_delete.beVisibleIf(!resetPossible && timerAdapter.itemCount > 1)
val drawableId = if (state is TimerState.Running) {
R.drawable.ic_pause_vector
@ -171,10 +147,15 @@ class TimerFragment : Fragment() {
view.timer_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
}
//
// fun updateAlarmSound(alarmSound: AlarmSound) {
// activity?.config?.timerSoundTitle = alarmSound.title
// activity?.config?.timerSoundUri = alarmSound.uri
// view.timer_sound.text = alarmSound.title
// }
fun updateAlarmSound(alarmSound: AlarmSound) {
val timer = timerAdapter.getItemAt(timer_view_pager.currentItem)
activity?.timerHelper?.insertOrUpdateTimer(timer.copy(soundTitle = alarmSound.title, soundUri = alarmSound.uri)) {
refreshTimers()
}
}
companion object {
private const val TAG = "TimerFragment"
}
}

View File

@ -45,6 +45,7 @@ const val TAB_CLOCK = 0
const val TAB_ALARM = 1
const val TAB_STOPWATCH = 2
const val TAB_TIMER = 3
const val TIMER_ID = "timer_position"
// stopwatch sorting
const val SORT_BY_LAP = 1

View File

@ -1,12 +1,11 @@
package com.simplemobiletools.clock.helpers
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.simplemobiletools.clock.extensions.gson.gson
import com.simplemobiletools.clock.models.StateWrapper
import com.simplemobiletools.clock.models.TimerState
class Converters {
private val gson = Gson()
@TypeConverter
fun jsonToTimerState(value: String): TimerState {

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.clock.helpers
import android.content.Context
import android.media.RingtoneManager
import android.util.Log
import com.simplemobiletools.clock.extensions.timerDb
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerState
@ -12,15 +13,22 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread
class TimerHelper(val context: Context) {
private val timerDao = context.timerDb
fun getTimers(callback: (notes: List<Timer>) -> Unit) {
fun getTimers(callback: (timers: List<Timer>) -> Unit) {
ensureBackgroundThread {
callback.invoke(timerDao.getTimers())
}
}
fun getTimer(timerId: Long, callback: (timer: Timer) -> Unit) {
ensureBackgroundThread {
callback.invoke(timerDao.getTimer(timerId))
}
}
fun insertOrUpdateTimer(timer: Timer, callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.insertOrUpdateTimer(timer)
val id = timerDao.insertOrUpdateTimer(timer)
Log.d(TAG, "insertOrUpdateTimer: $id")
callback.invoke()
}
}
@ -48,4 +56,8 @@ class TimerHelper(val context: Context) {
callback.invoke()
}
}
companion object {
private const val TAG = "TimerHelper"
}
}

View File

@ -12,6 +12,9 @@ interface TimerDao {
@Query("SELECT * FROM timers")
fun getTimers(): List<Timer>
@Query("SELECT * FROM timers WHERE id=:id")
fun getTimer(id: Long): Timer
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdateTimer(timer: Timer): Long

View File

@ -0,0 +1,9 @@
package com.simplemobiletools.clock.models
sealed class TimerEvent(open val timerId: Long) {
data class Reset(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Start(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Pause(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Finish(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Refresh(override val timerId: Long) : TimerEvent(timerId)
}

View File

@ -2,10 +2,7 @@ package com.simplemobiletools.clock.models
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

@ -76,7 +76,7 @@ class TimerService : Service() {
.setContentTitle(label)
.setContentText(formattedDuration)
.setSmallIcon(R.drawable.ic_timer)
.setContentIntent(this.getOpenTimerTabIntent())
.setContentIntent(this.getOpenTimerTabIntent(0))
.setPriority(Notification.PRIORITY_DEFAULT)
.setSound(null)
.setOngoing(true)

View File

@ -24,7 +24,7 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_reset_vector"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/timer_play_pause"
app:layout_constraintEnd_toStartOf="@+id/timer_play_pause"
app:layout_constraintStart_toStartOf="parent"

View File

@ -1,111 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_time"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/normal_margin"
android:background="?attr/selectableItemBackground"
android:gravity="center_horizontal"
android:padding="@dimen/small_margin"
android:textSize="@dimen/stopwatch_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00:00" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_time"
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_initial_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_timer"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_time"
tools:text="05:00" />
<RelativeLayout
android:id="@+id/timer_vibrate_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/activity_margin"
app:layout_constraintTop_toBottomOf="@+id/timer_initial_time">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/timer_vibrate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/normal_margin"
android:background="?attr/selectableItemBackground"
android:gravity="center_horizontal"
android:padding="@dimen/small_margin"
android:textSize="@dimen/stopwatch_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00:00" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_initial_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_timer"
android:background="@null"
android:clickable="false"
android:drawableStart="@drawable/ic_vibrate_vector"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_time"
tools:text="05:00" />
android:text="@string/vibrate"
android:textSize="@dimen/bigger_text_size" />
<RelativeLayout
android:id="@+id/timer_vibrate_holder"
</RelativeLayout>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_bell_vector"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_vibrate_holder"
tools:text="Default alarm" />
<LinearLayout
android:id="@+id/timer_label_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timer_sound"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="spread_inside">
<ImageView
android:id="@+id/timer_label_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_label_vector" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/timer_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/activity_margin"
app:layout_constraintTop_toBottomOf="@+id/timer_initial_time">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/timer_vibrate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:drawableStart="@drawable/ic_vibrate_vector"
android:drawablePadding="@dimen/normal_margin"
android:text="@string/vibrate"
android:textSize="@dimen/bigger_text_size" />
</RelativeLayout>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_bell_vector"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_vibrate_holder"
tools:text="Default alarm" />
<LinearLayout
android:id="@+id/timer_label_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timer_sound"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="spread_inside">
<ImageView
android:id="@+id/timer_label_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_label_vector" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/timer_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:hint="@string/label"
android:maxLines="1"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
android:layout_marginStart="10dp"
android:hint="@string/label"
android:maxLines="1"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>