Merge pull request #338 from sdex/run_stopwatch_in_background
Allow stopwatch to run in the background
This commit is contained in:
commit
c15040c2f0
|
@ -100,6 +100,8 @@
|
|||
|
||||
<service android:name=".services.TimerService" />
|
||||
|
||||
<service android:name=".services.StopwatchService" />
|
||||
|
||||
<receiver android:name=".receivers.AlarmReceiver" />
|
||||
|
||||
<receiver android:name=".receivers.HideTimerReceiver" />
|
||||
|
|
|
@ -12,9 +12,13 @@ import androidx.lifecycle.OnLifecycleEvent
|
|||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.simplemobiletools.clock.extensions.*
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch.State
|
||||
import com.simplemobiletools.clock.models.TimerEvent
|
||||
import com.simplemobiletools.clock.models.TimerState
|
||||
import com.simplemobiletools.clock.services.StopwatchStopService
|
||||
import com.simplemobiletools.clock.services.TimerStopService
|
||||
import com.simplemobiletools.clock.services.startStopwatchService
|
||||
import com.simplemobiletools.clock.services.startTimerService
|
||||
import com.simplemobiletools.commons.extensions.checkUseEnglish
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -49,6 +53,9 @@ class App : Application(), LifecycleObserver {
|
|||
startTimerService(this)
|
||||
}
|
||||
}
|
||||
if (Stopwatch.state == State.RUNNING) {
|
||||
startStopwatchService(this)
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
|
@ -62,6 +69,9 @@ class App : Application(), LifecycleObserver {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (Stopwatch.state == State.RUNNING) {
|
||||
EventBus.getDefault().post(StopwatchStopService)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
|
|
|
@ -159,6 +159,12 @@ fun Context.getOpenTimerTabIntent(timerId: Int): PendingIntent {
|
|||
return PendingIntent.getActivity(this, timerId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
fun Context.getOpenStopwatchTabIntent(): PendingIntent {
|
||||
val intent = getLaunchIntent() ?: Intent(this, SplashActivity::class.java)
|
||||
intent.putExtra(OPEN_TAB, TAB_STOPWATCH)
|
||||
return PendingIntent.getActivity(this, OPEN_STOPWATCH_TAB_INTENT_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
fun Context.getAlarmIntent(alarm: Alarm): PendingIntent {
|
||||
val intent = Intent(this, AlarmReceiver::class.java)
|
||||
intent.putExtra(ALARM_ID, alarm.id)
|
||||
|
|
|
@ -4,45 +4,25 @@ import android.graphics.Bitmap
|
|||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.activities.SimpleActivity
|
||||
import com.simplemobiletools.clock.adapters.StopwatchAdapter
|
||||
import com.simplemobiletools.clock.extensions.config
|
||||
import com.simplemobiletools.clock.extensions.formatStopwatchTime
|
||||
import com.simplemobiletools.clock.helpers.SORT_BY_LAP
|
||||
import com.simplemobiletools.clock.helpers.SORT_BY_LAP_TIME
|
||||
import com.simplemobiletools.clock.helpers.SORT_BY_TOTAL_TIME
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch
|
||||
import com.simplemobiletools.clock.models.Lap
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
|
||||
import kotlinx.android.synthetic.main.fragment_stopwatch.view.*
|
||||
|
||||
class StopwatchFragment : Fragment() {
|
||||
private val UPDATE_INTERVAL = 10L
|
||||
private val WAS_RUNNING = "was_running"
|
||||
private val TOTAL_TICKS = "total_ticks"
|
||||
private val CURRENT_TICKS = "current_ticks"
|
||||
private val LAP_TICKS = "lap_ticks"
|
||||
private val CURRENT_LAP = "current_lap"
|
||||
private val LAPS = "laps"
|
||||
private val SORTING = "sorting"
|
||||
|
||||
private val updateHandler = Handler()
|
||||
private var uptimeAtStart = 0L
|
||||
private var totalTicks = 0
|
||||
private var currentTicks = 0 // ticks that reset at pause
|
||||
private var lapTicks = 0
|
||||
private var currentLap = 1
|
||||
private var isRunning = false
|
||||
private var sorting = SORT_BY_LAP or SORT_DESCENDING
|
||||
private var laps = ArrayList<Lap>()
|
||||
|
||||
private var storedTextColor = 0
|
||||
|
||||
|
@ -51,6 +31,8 @@ class StopwatchFragment : Fragment() {
|
|||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
storeStateVariables()
|
||||
val sorting = requireContext().config.stopwatchLapsSort
|
||||
Lap.sorting = sorting
|
||||
view = (inflater.inflate(R.layout.fragment_stopwatch, container, false) as ViewGroup).apply {
|
||||
stopwatch_time.setOnClickListener {
|
||||
togglePlayPause()
|
||||
|
@ -78,20 +60,7 @@ class StopwatchFragment : Fragment() {
|
|||
|
||||
stopwatch_lap.setOnClickListener {
|
||||
stopwatch_sorting_indicators_holder.beVisible()
|
||||
if (laps.isEmpty()) {
|
||||
val lap = Lap(currentLap++, lapTicks * UPDATE_INTERVAL, totalTicks * UPDATE_INTERVAL)
|
||||
laps.add(0, lap)
|
||||
lapTicks = 0
|
||||
} else {
|
||||
laps.first().apply {
|
||||
lapTime = lapTicks * UPDATE_INTERVAL
|
||||
totalTime = totalTicks * UPDATE_INTERVAL
|
||||
}
|
||||
}
|
||||
|
||||
val lap = Lap(currentLap++, lapTicks * UPDATE_INTERVAL, totalTicks * UPDATE_INTERVAL)
|
||||
laps.add(0, lap)
|
||||
lapTicks = 0
|
||||
Stopwatch.lap()
|
||||
updateLaps()
|
||||
}
|
||||
|
||||
|
@ -100,11 +69,10 @@ class StopwatchFragment : Fragment() {
|
|||
changeSorting(it)
|
||||
}
|
||||
}
|
||||
Lap.sorting = sorting
|
||||
stopwatch_list.adapter = stopwatchAdapter
|
||||
}
|
||||
|
||||
updateSortingIndicators()
|
||||
updateSortingIndicators(sorting)
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -116,64 +84,25 @@ class StopwatchFragment : Fragment() {
|
|||
if (storedTextColor != configTextColor) {
|
||||
stopwatchAdapter.updateTextColor(configTextColor)
|
||||
}
|
||||
|
||||
Stopwatch.addUpdateListener(updateListener)
|
||||
updateLaps()
|
||||
view.stopwatch_sorting_indicators_holder.beVisibleIf(Stopwatch.laps.isNotEmpty())
|
||||
if (Stopwatch.laps.isNotEmpty()) {
|
||||
updateSorting(Lap.sorting)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
storeStateVariables()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (isRunning && activity?.isChangingConfigurations == false) {
|
||||
context?.toast(R.string.stopwatch_stopped)
|
||||
}
|
||||
isRunning = false
|
||||
updateHandler.removeCallbacks(updateRunnable)
|
||||
Stopwatch.removeUpdateListener(updateListener)
|
||||
}
|
||||
|
||||
private fun storeStateVariables() {
|
||||
storedTextColor = requireContext().getProperTextColor()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.apply {
|
||||
putBoolean(WAS_RUNNING, isRunning)
|
||||
putInt(TOTAL_TICKS, totalTicks)
|
||||
putInt(CURRENT_TICKS, currentTicks)
|
||||
putInt(LAP_TICKS, lapTicks)
|
||||
putInt(CURRENT_LAP, currentLap)
|
||||
putInt(SORTING, sorting)
|
||||
putString(LAPS, Gson().toJson(laps))
|
||||
super.onSaveInstanceState(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
savedInstanceState?.apply {
|
||||
isRunning = getBoolean(WAS_RUNNING, false)
|
||||
totalTicks = getInt(TOTAL_TICKS, 0)
|
||||
currentTicks = getInt(CURRENT_TICKS, 0)
|
||||
lapTicks = getInt(LAP_TICKS, 0)
|
||||
currentLap = getInt(CURRENT_LAP, 0)
|
||||
sorting = getInt(SORTING, SORT_BY_LAP or SORT_DESCENDING)
|
||||
|
||||
val lapsToken = object : TypeToken<List<Lap>>() {}.type
|
||||
laps = Gson().fromJson(getString(LAPS), lapsToken)
|
||||
|
||||
if (laps.isNotEmpty()) {
|
||||
view.stopwatch_sorting_indicators_holder.beVisibleIf(laps.isNotEmpty())
|
||||
updateSorting()
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
uptimeAtStart = SystemClock.uptimeMillis() - currentTicks * UPDATE_INTERVAL
|
||||
updateStopwatchState(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
val properPrimaryColor = requireContext().getProperPrimaryColor()
|
||||
view.apply {
|
||||
|
@ -181,60 +110,30 @@ class StopwatchFragment : Fragment() {
|
|||
stopwatch_play_pause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, properPrimaryColor)
|
||||
stopwatch_reset.applyColorFilter(requireContext().getProperTextColor())
|
||||
}
|
||||
|
||||
updateIcons()
|
||||
updateDisplayedText()
|
||||
}
|
||||
|
||||
private fun updateIcons() {
|
||||
val drawableId = if (isRunning) R.drawable.ic_pause_vector else R.drawable.ic_play_vector
|
||||
private fun updateIcons(state: Stopwatch.State) {
|
||||
val drawableId = if (state == Stopwatch.State.RUNNING) R.drawable.ic_pause_vector else R.drawable.ic_play_vector
|
||||
val iconColor = if (requireContext().getProperPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE
|
||||
view.stopwatch_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
|
||||
}
|
||||
|
||||
private fun togglePlayPause() {
|
||||
isRunning = !isRunning
|
||||
updateStopwatchState(true)
|
||||
Stopwatch.toggle(true)
|
||||
}
|
||||
|
||||
private fun updateStopwatchState(setUptimeAtStart: Boolean) {
|
||||
updateIcons()
|
||||
view.stopwatch_lap.beVisibleIf(isRunning)
|
||||
|
||||
if (isRunning) {
|
||||
updateHandler.post(updateRunnable)
|
||||
view.stopwatch_reset.beVisible()
|
||||
if (setUptimeAtStart) {
|
||||
uptimeAtStart = SystemClock.uptimeMillis()
|
||||
}
|
||||
} else {
|
||||
val prevSessionsMS = (totalTicks - currentTicks) * UPDATE_INTERVAL
|
||||
val totalDuration = SystemClock.uptimeMillis() - uptimeAtStart + prevSessionsMS
|
||||
updateHandler.removeCallbacksAndMessages(null)
|
||||
view.stopwatch_time.text = totalDuration.formatStopwatchTime(true)
|
||||
currentTicks = 0
|
||||
totalTicks--
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDisplayedText() {
|
||||
view.stopwatch_time.text = (totalTicks * UPDATE_INTERVAL).formatStopwatchTime(false)
|
||||
if (currentLap > 1) {
|
||||
stopwatchAdapter.updateLastField(lapTicks * UPDATE_INTERVAL, totalTicks * UPDATE_INTERVAL)
|
||||
private fun updateDisplayedText(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
|
||||
view.stopwatch_time.text = totalTime.formatStopwatchTime(useLongerMSFormat)
|
||||
if (Stopwatch.laps.isNotEmpty() && lapTime != -1L) {
|
||||
stopwatchAdapter.updateLastField(lapTime, totalTime)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetStopwatch() {
|
||||
updateHandler.removeCallbacksAndMessages(null)
|
||||
isRunning = false
|
||||
currentTicks = 0
|
||||
totalTicks = 0
|
||||
currentLap = 1
|
||||
lapTicks = 0
|
||||
laps.clear()
|
||||
updateIcons()
|
||||
stopwatchAdapter.updateItems(laps)
|
||||
Stopwatch.reset()
|
||||
|
||||
updateLaps()
|
||||
view.apply {
|
||||
stopwatch_reset.beGone()
|
||||
stopwatch_lap.beGone()
|
||||
|
@ -244,21 +143,22 @@ class StopwatchFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun changeSorting(clickedValue: Int) {
|
||||
sorting = if (sorting and clickedValue != 0) {
|
||||
sorting.flipBit(SORT_DESCENDING)
|
||||
val sorting = if (Lap.sorting and clickedValue != 0) {
|
||||
Lap.sorting.flipBit(SORT_DESCENDING)
|
||||
} else {
|
||||
clickedValue or SORT_DESCENDING
|
||||
}
|
||||
updateSorting()
|
||||
updateSorting(sorting)
|
||||
}
|
||||
|
||||
private fun updateSorting() {
|
||||
updateSortingIndicators()
|
||||
private fun updateSorting(sorting: Int) {
|
||||
updateSortingIndicators(sorting)
|
||||
Lap.sorting = sorting
|
||||
requireContext().config.stopwatchLapsSort = sorting
|
||||
updateLaps()
|
||||
}
|
||||
|
||||
private fun updateSortingIndicators() {
|
||||
private fun updateSortingIndicators(sorting: Int) {
|
||||
var bitmap = requireContext().resources.getColoredBitmap(R.drawable.ic_sorting_triangle_vector, requireContext().getProperPrimaryColor())
|
||||
view.apply {
|
||||
stopwatch_sorting_indicator_1.beInvisibleIf(sorting and SORT_BY_LAP == 0)
|
||||
|
@ -281,20 +181,18 @@ class StopwatchFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun updateLaps() {
|
||||
stopwatchAdapter.updateItems(laps)
|
||||
stopwatchAdapter.updateItems(Stopwatch.laps)
|
||||
}
|
||||
|
||||
private val updateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (isRunning) {
|
||||
if (totalTicks % 10 == 0) {
|
||||
updateDisplayedText()
|
||||
}
|
||||
totalTicks++
|
||||
currentTicks++
|
||||
lapTicks++
|
||||
updateHandler.postAtTime(this, uptimeAtStart + currentTicks * UPDATE_INTERVAL)
|
||||
}
|
||||
private val updateListener = object : Stopwatch.UpdateListener {
|
||||
override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
|
||||
updateDisplayedText(totalTime, lapTime, useLongerMSFormat)
|
||||
}
|
||||
|
||||
override fun onStateChanged(state: Stopwatch.State) {
|
||||
updateIcons(state)
|
||||
view.stopwatch_lap.beVisibleIf(state == Stopwatch.State.RUNNING)
|
||||
view.stopwatch_reset.beVisibleIf(state != Stopwatch.State.STOPPED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.simplemobiletools.clock.models.TimerState
|
|||
import com.simplemobiletools.commons.extensions.getDefaultAlarmSound
|
||||
import com.simplemobiletools.commons.extensions.getDefaultAlarmTitle
|
||||
import com.simplemobiletools.commons.helpers.BaseConfig
|
||||
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
|
||||
|
||||
class Config(context: Context) : BaseConfig(context) {
|
||||
companion object {
|
||||
|
@ -81,4 +82,8 @@ class Config(context: Context) : BaseConfig(context) {
|
|||
var timerChannelId: String?
|
||||
get() = prefs.getString(TIMER_CHANNEL_ID, null)
|
||||
set(id) = prefs.edit().putString(TIMER_CHANNEL_ID, id).apply()
|
||||
|
||||
var stopwatchLapsSort: Int
|
||||
get() = prefs.getInt(STOPWATCH_LAPS_SORT_BY, SORT_BY_LAP or SORT_DESCENDING)
|
||||
set(stopwatchLapsSort) = prefs.edit().putInt(STOPWATCH_LAPS_SORT_BY, stopwatchLapsSort).apply()
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ const val ALARM_LAST_CONFIG = "alarm_last_config"
|
|||
const val TIMER_LAST_CONFIG = "timer_last_config"
|
||||
const val INCREASE_VOLUME_GRADUALLY = "increase_volume_gradually"
|
||||
const val ALARMS_SORT_BY = "alarms_sort_by"
|
||||
const val STOPWATCH_LAPS_SORT_BY = "stopwatch_laps_sort_by"
|
||||
|
||||
const val TABS_COUNT = 4
|
||||
const val EDITED_TIME_ZONE_SEPARATOR = ":"
|
||||
|
@ -33,10 +34,12 @@ const val DEFAULT_MAX_TIMER_REMINDER_SECS = 60
|
|||
const val PICK_AUDIO_FILE_INTENT_ID = 9994
|
||||
const val REMINDER_ACTIVITY_INTENT_ID = 9995
|
||||
const val OPEN_ALARMS_TAB_INTENT_ID = 9996
|
||||
const val OPEN_STOPWATCH_TAB_INTENT_ID = 9993
|
||||
const val UPDATE_WIDGET_INTENT_ID = 9997
|
||||
const val OPEN_APP_INTENT_ID = 9998
|
||||
const val ALARM_NOTIF_ID = 9998
|
||||
const val TIMER_RUNNING_NOTIF_ID = 10000
|
||||
const val STOPWATCH_RUNNING_NOTIF_ID = 10001
|
||||
|
||||
const val OPEN_TAB = "open_tab"
|
||||
const val TAB_CLOCK = 0
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package com.simplemobiletools.clock.helpers
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import com.simplemobiletools.clock.models.Lap
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
private const val UPDATE_INTERVAL = 10L
|
||||
|
||||
object Stopwatch {
|
||||
|
||||
private val updateHandler = Handler(Looper.getMainLooper())
|
||||
private var uptimeAtStart = 0L
|
||||
private var totalTicks = 0
|
||||
private var currentTicks = 0 // ticks that reset at pause
|
||||
private var lapTicks = 0
|
||||
private var currentLap = 1
|
||||
val laps = ArrayList<Lap>()
|
||||
var state = State.STOPPED
|
||||
private set(value) {
|
||||
field = value
|
||||
for (listener in updateListeners) {
|
||||
listener.onStateChanged(value)
|
||||
}
|
||||
}
|
||||
private var updateListeners = CopyOnWriteArraySet<UpdateListener>()
|
||||
|
||||
fun reset() {
|
||||
updateHandler.removeCallbacksAndMessages(null)
|
||||
state = State.STOPPED
|
||||
currentTicks = 0
|
||||
totalTicks = 0
|
||||
currentLap = 1
|
||||
lapTicks = 0
|
||||
laps.clear()
|
||||
}
|
||||
|
||||
fun toggle(setUptimeAtStart: Boolean) {
|
||||
if (state != State.RUNNING) {
|
||||
state = State.RUNNING
|
||||
updateHandler.post(updateRunnable)
|
||||
if (setUptimeAtStart) {
|
||||
uptimeAtStart = SystemClock.uptimeMillis()
|
||||
}
|
||||
} else {
|
||||
state = State.PAUSED
|
||||
val prevSessionsMS = (totalTicks - currentTicks) * UPDATE_INTERVAL
|
||||
val totalDuration = SystemClock.uptimeMillis() - uptimeAtStart + prevSessionsMS
|
||||
updateHandler.removeCallbacksAndMessages(null)
|
||||
currentTicks = 0
|
||||
totalTicks--
|
||||
for (listener in updateListeners) {
|
||||
listener.onUpdate(totalDuration, -1, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun lap() {
|
||||
if (laps.isEmpty()) {
|
||||
val lap = Lap(currentLap++, lapTicks * UPDATE_INTERVAL, totalTicks * UPDATE_INTERVAL)
|
||||
laps.add(0, lap)
|
||||
lapTicks = 0
|
||||
} else {
|
||||
laps.first().apply {
|
||||
lapTime = lapTicks * UPDATE_INTERVAL
|
||||
totalTime = totalTicks * UPDATE_INTERVAL
|
||||
}
|
||||
}
|
||||
|
||||
val lap = Lap(currentLap++, lapTicks * UPDATE_INTERVAL, totalTicks * UPDATE_INTERVAL)
|
||||
laps.add(0, lap)
|
||||
lapTicks = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a update listener to the stopwatch. The listener gets the current state
|
||||
* immediately after adding. To avoid memory leaks the listener should be removed
|
||||
* from the stopwatch.
|
||||
* @param updateListener the listener
|
||||
*/
|
||||
fun addUpdateListener(updateListener: UpdateListener) {
|
||||
updateListeners.add(updateListener)
|
||||
updateListener.onUpdate(
|
||||
totalTicks * UPDATE_INTERVAL,
|
||||
lapTicks * UPDATE_INTERVAL,
|
||||
state != State.STOPPED
|
||||
)
|
||||
updateListener.onStateChanged(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the listener from the stopwatch
|
||||
* @param updateListener the listener
|
||||
*/
|
||||
fun removeUpdateListener(updateListener: UpdateListener) {
|
||||
updateListeners.remove(updateListener)
|
||||
}
|
||||
|
||||
private val updateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (state == State.RUNNING) {
|
||||
if (totalTicks % 10 == 0) {
|
||||
for (listener in updateListeners) {
|
||||
listener.onUpdate(
|
||||
totalTicks * UPDATE_INTERVAL,
|
||||
lapTicks * UPDATE_INTERVAL,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
totalTicks++
|
||||
currentTicks++
|
||||
lapTicks++
|
||||
updateHandler.postAtTime(this, uptimeAtStart + currentTicks * UPDATE_INTERVAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class State {
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
interface UpdateListener {
|
||||
fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean)
|
||||
fun onStateChanged(state: State)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package com.simplemobiletools.clock.services
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.extensions.getFormattedDuration
|
||||
import com.simplemobiletools.clock.extensions.getOpenStopwatchTabIntent
|
||||
import com.simplemobiletools.clock.helpers.STOPWATCH_RUNNING_NOTIF_ID
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch.State
|
||||
import com.simplemobiletools.clock.helpers.Stopwatch.UpdateListener
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class StopwatchService : Service() {
|
||||
|
||||
private val bus = EventBus.getDefault()
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
private var isStopping = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
bus.register(this)
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationBuilder = getServiceNotificationBuilder(
|
||||
getString(R.string.app_name),
|
||||
getString(R.string.stopwatch)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
isStopping = false
|
||||
startForeground(
|
||||
STOPWATCH_RUNNING_NOTIF_ID,
|
||||
notificationBuilder.build()
|
||||
)
|
||||
Stopwatch.addUpdateListener(updateListener)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bus.unregister(this)
|
||||
Stopwatch.removeUpdateListener(updateListener)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: StopwatchStopService) {
|
||||
isStopping = true
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
private fun getServiceNotificationBuilder(
|
||||
title: String,
|
||||
contentText: String
|
||||
): NotificationCompat.Builder {
|
||||
val channelId = "simple_alarm_stopwatch"
|
||||
val label = getString(R.string.stopwatch)
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
NotificationChannel(channelId, label, importance).apply {
|
||||
setSound(null, null)
|
||||
notificationManager.createNotificationChannel(this)
|
||||
}
|
||||
return NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle(title)
|
||||
.setContentText(contentText)
|
||||
.setSmallIcon(R.drawable.ic_stopwatch_vector)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(getOpenStopwatchTabIntent())
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
}
|
||||
|
||||
private fun updateNotification(totalTime: Long) {
|
||||
val formattedDuration = totalTime.getFormattedDuration()
|
||||
notificationBuilder.setContentTitle(formattedDuration)
|
||||
.setContentText(getString(R.string.stopwatch))
|
||||
notificationManager.notify(
|
||||
STOPWATCH_RUNNING_NOTIF_ID,
|
||||
notificationBuilder.build()
|
||||
)
|
||||
}
|
||||
|
||||
private val updateListener = object : UpdateListener {
|
||||
override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
|
||||
if (!isStopping) {
|
||||
updateNotification(totalTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChanged(state: State) {
|
||||
if (state == State.STOPPED) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startStopwatchService(context: Context) {
|
||||
ContextCompat.startForegroundService(context, Intent(context, StopwatchService::class.java))
|
||||
}
|
||||
|
||||
object StopwatchStopService
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etiket</string>
|
||||
<string name="no_days_selected">Gün seçilməyib</string>
|
||||
<string name="timer">Sayğac</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Dövrə</string>
|
||||
<string name="stopwatch_stopped">Saniyəölçən dayandı</string>
|
||||
<string name="timer_stopped">Sayğaç dayandı</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Štítek</string>
|
||||
<string name="no_days_selected">Nebyly označeny žádné dny</string>
|
||||
<string name="timer">Časovač</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Mezičas</string>
|
||||
<string name="stopwatch_stopped">Stopky byly zastaveny</string>
|
||||
<string name="timer_stopped">Časovač byl zastaven</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Label</string>
|
||||
<string name="no_days_selected">Dim diwrnodau wedi\'u dewis</string>
|
||||
<string name="timer">Amserydd</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Lap</string>
|
||||
<string name="stopwatch_stopped">Cafodd y stopwats ei stopio</string>
|
||||
<string name="timer_stopped">Cafodd yr amserydd ei stopio</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etiket</string>
|
||||
<string name="no_days_selected">Der er ikke valgt nogen dage</string>
|
||||
<string name="timer">Æggeur</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Mellemtid</string>
|
||||
<string name="stopwatch_stopped">Stopuret er standset</string>
|
||||
<string name="timer_stopped">Æggeuret er standset</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Label</string>
|
||||
<string name="no_days_selected">Keine Tage ausgewählt</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Runde</string>
|
||||
<string name="stopwatch_stopped">Die Stoppuhr wurde angehalten</string>
|
||||
<string name="timer_stopped">Der Timer wurde angehalten</string>
|
||||
|
@ -47,4 +48,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Ετικέτα</string>
|
||||
<string name="no_days_selected">Δεν έχουν επιλεγεί ημέρες</string>
|
||||
<string name="timer">Χρονόμετρο</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Γύρος</string>
|
||||
<string name="stopwatch_stopped">Ο χρονοδιακόπτης σταμάτησε</string>
|
||||
<string name="timer_stopped">Το χρονόμετρο σταμάτησε</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etiqueta</string>
|
||||
<string name="no_days_selected">Ningún día seleccionado</string>
|
||||
<string name="timer">Temporizador</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Vuelta</string>
|
||||
<string name="stopwatch_stopped">El cronómetro s eha detenido</string>
|
||||
<string name="timer_stopped">El temporizador se ha detenido</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Silt</string>
|
||||
<string name="no_days_selected">Ühtegi päeva pole valitud</string>
|
||||
<string name="timer">Taimer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Ringi aeg</string>
|
||||
<string name="stopwatch_stopped">Stopper on kinni</string>
|
||||
<string name="timer_stopped">Taimer on peatunud</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etiketa</string>
|
||||
<string name="no_days_selected">Ez duzu egunik hautatu</string>
|
||||
<string name="timer">Tenporizagailua</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Bira</string>
|
||||
<string name="stopwatch_stopped">Kronometroa gelditu da</string>
|
||||
<string name="timer_stopped">Tenporizagailua gelditu da</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Tunniste</string>
|
||||
<string name="no_days_selected">Ei päiviä valittuna</string>
|
||||
<string name="timer">Ajastin</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Kierrosaika</string>
|
||||
<string name="stopwatch_stopped">Sekuntikello pysäytetty</string>
|
||||
<string name="timer_stopped">Ajastin on pysäytetty</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Titre</string>
|
||||
<string name="no_days_selected">Aucun jour sélectionné</string>
|
||||
<string name="timer">Minuterie</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Tour</string>
|
||||
<string name="stopwatch_stopped">Chronomètre arrêté</string>
|
||||
<string name="timer_stopped">La minuterie a été arrêtée</string>
|
||||
|
@ -47,4 +48,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Etiqueta</string>
|
||||
<string name="no_days_selected">Ningún día seleccionado</string>
|
||||
<string name="timer">Temporizador</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Volta</string>
|
||||
<string name="stopwatch_stopped">Cronómetro parado</string>
|
||||
<string name="timer_stopped">Temporizador parado</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Oznaka</string>
|
||||
<string name="no_days_selected">Nije odabran nijedan dan</string>
|
||||
<string name="timer">Brojač</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Krug</string>
|
||||
<string name="stopwatch_stopped">Štoperica je zaustavljena</string>
|
||||
<string name="timer_stopped">Brojač je zaustavljen</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Címke</string>
|
||||
<string name="no_days_selected">Nincs nap kiválasztva</string>
|
||||
<string name="timer">Időzítő</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Kör</string>
|
||||
<string name="stopwatch_stopped">A stopper leállt</string>
|
||||
<string name="timer_stopped">Az időzítő leállt</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Label</string>
|
||||
<string name="no_days_selected">Tidak ada hari yang dipilih</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Putaran</string>
|
||||
<string name="stopwatch_stopped">Stopwatch telah berhenti</string>
|
||||
<string name="timer_stopped">Timer telah berhenti</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Etichetta</string>
|
||||
<string name="no_days_selected">Nessun giorno selezionato</string>
|
||||
<string name="timer">Contaminuti</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Parziale</string>
|
||||
<string name="stopwatch_stopped">Il cronometro è stato fermato</string>
|
||||
<string name="timer_stopped">Il contaminuti è stato fermato</string>
|
||||
|
@ -68,4 +69,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">ラベル</string>
|
||||
<string name="no_days_selected">曜日が選択されていません</string>
|
||||
<string name="timer">タイマー</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">ラップ</string>
|
||||
<string name="stopwatch_stopped">ストップウォッチが停止しました</string>
|
||||
<string name="timer_stopped">タイマーが停止しました</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etiketė</string>
|
||||
<string name="no_days_selected">Nepasirinkta nė vienos dienos</string>
|
||||
<string name="timer">Laikmatis</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Etapas</string>
|
||||
<string name="stopwatch_stopped">Chronometras buvo sustabdytas</string>
|
||||
<string name="timer_stopped">Laikmatis buvo sustabdytas</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">അടിക്കുറിപ്പ് </string>
|
||||
<string name="no_days_selected">ദിവസങ്ങളൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല</string>
|
||||
<string name="timer">ടൈമർ</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">ലാപ്</string>
|
||||
<string name="stopwatch_stopped">Stopwatch has been stopped</string>
|
||||
<string name="timer_stopped">സ്റ്റോപ്പ് വാച്ച് നിർത്തി</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Påskrift</string>
|
||||
<string name="no_days_selected">Ingen dager valgte</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Runde</string>
|
||||
<string name="stopwatch_stopped">Stoppeklokke er stoppet</string>
|
||||
<string name="timer_stopped">Timer er stoppet</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Label</string>
|
||||
<string name="no_days_selected">Geen dagen geselecteerd</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Ronde</string>
|
||||
<string name="stopwatch_stopped">Stopwatch is gestopt</string>
|
||||
<string name="timer_stopped">Timer is gestopt</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Etykieta</string>
|
||||
<string name="no_days_selected">Nie wybrano dni</string>
|
||||
<string name="timer">Minutnik</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Okrążenie</string>
|
||||
<string name="stopwatch_stopped">Stoper został zatrzymany</string>
|
||||
<string name="timer_stopped">Minutnik został zatrzymany</string>
|
||||
|
@ -49,4 +50,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Legenda</string>
|
||||
<string name="no_days_selected">Nenhum dia selecionado</string>
|
||||
<string name="timer">Temporizador</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Volta</string>
|
||||
<string name="stopwatch_stopped">Cronómetro parado</string>
|
||||
<string name="timer_stopped">Temporizador parado</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Etichetă</string>
|
||||
<string name="no_days_selected">Nici o zi selectată</string>
|
||||
<string name="timer">Temporizator</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">O tură</string>
|
||||
<string name="stopwatch_stopped">Cronometrul a fost oprit</string>
|
||||
<string name="timer_stopped">Temporizatorul a fost oprit</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Метка</string>
|
||||
<string name="no_days_selected">Дни не выбраны</string>
|
||||
<string name="timer">Таймер</string>
|
||||
<string name="stopwatch">Секундомер</string>
|
||||
<string name="lap">Круг</string>
|
||||
<string name="stopwatch_stopped">Секундомер остановлен</string>
|
||||
<string name="timer_stopped">Таймер остановлен</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Štítok</string>
|
||||
<string name="no_days_selected">Neboli označené žiadne dni</string>
|
||||
<string name="timer">Časovač</string>
|
||||
<string name="stopwatch">Stopky</string>
|
||||
<string name="lap">Okruh</string>
|
||||
<string name="stopwatch_stopped">Stopky boli zastavené</string>
|
||||
<string name="timer_stopped">Časovač bol zastavený</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Etikett</string>
|
||||
<string name="no_days_selected">Inga dagar har valts</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Varv</string>
|
||||
<string name="stopwatch_stopped">Stoppuret har stoppats</string>
|
||||
<string name="timer_stopped">Timern har stoppats</string>
|
||||
|
@ -70,4 +71,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">Etiket</string>
|
||||
<string name="no_days_selected">Hiçbir gün seçilmedi</string>
|
||||
<string name="timer">Zamanlayıcı</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Tur</string>
|
||||
<string name="stopwatch_stopped">Kronometre durduruldu</string>
|
||||
<string name="timer_stopped">Zamanlayıcı durduruldu</string>
|
||||
|
@ -70,4 +71,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Мітка</string>
|
||||
<string name="no_days_selected">Дні не обрано</string>
|
||||
<string name="timer">Таймер</string>
|
||||
<string name="stopwatch">Секундомір</string>
|
||||
<string name="lap">Інтервал</string>
|
||||
<string name="stopwatch_stopped">Секундомір зупинено</string>
|
||||
<string name="timer_stopped">Таймер зупинено</string>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="label">标签</string>
|
||||
<string name="no_days_selected">未选择哪一天</string>
|
||||
<string name="timer">定时器</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">分段</string>
|
||||
<string name="stopwatch_stopped">秒表已停止</string>
|
||||
<string name="timer_stopped">定时器已停止</string>
|
||||
|
@ -69,4 +70,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">標籤</string>
|
||||
<string name="no_days_selected">未選擇哪一天</string>
|
||||
<string name="timer">計時器</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">分段</string>
|
||||
<string name="stopwatch_stopped">碼錶已停止</string>
|
||||
<string name="timer_stopped">計時器已停止</string>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<string name="label">Label</string>
|
||||
<string name="no_days_selected">No days selected</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="stopwatch">Stopwatch</string>
|
||||
<string name="lap">Lap</string>
|
||||
<string name="stopwatch_stopped">Stopwatch has been stopped</string>
|
||||
<string name="timer_stopped">Timer has been stopped</string>
|
||||
|
|
Loading…
Reference in New Issue