Merge pull request #338 from sdex/run_stopwatch_in_background

Allow stopwatch to run in the background
This commit is contained in:
Tibor Kaputa
2022-05-02 15:25:32 +02:00
committed by GitHub
40 changed files with 351 additions and 149 deletions

View File

@ -100,6 +100,8 @@
<service android:name=".services.TimerService" /> <service android:name=".services.TimerService" />
<service android:name=".services.StopwatchService" />
<receiver android:name=".receivers.AlarmReceiver" /> <receiver android:name=".receivers.AlarmReceiver" />
<receiver android:name=".receivers.HideTimerReceiver" /> <receiver android:name=".receivers.HideTimerReceiver" />

View File

@ -12,9 +12,13 @@ import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import com.simplemobiletools.clock.extensions.* 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.TimerEvent
import com.simplemobiletools.clock.models.TimerState import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.clock.services.StopwatchStopService
import com.simplemobiletools.clock.services.TimerStopService import com.simplemobiletools.clock.services.TimerStopService
import com.simplemobiletools.clock.services.startStopwatchService
import com.simplemobiletools.clock.services.startTimerService import com.simplemobiletools.clock.services.startTimerService
import com.simplemobiletools.commons.extensions.checkUseEnglish import com.simplemobiletools.commons.extensions.checkUseEnglish
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -49,6 +53,9 @@ class App : Application(), LifecycleObserver {
startTimerService(this) startTimerService(this)
} }
} }
if (Stopwatch.state == State.RUNNING) {
startStopwatchService(this)
}
} }
@OnLifecycleEvent(Lifecycle.Event.ON_START) @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) @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -159,6 +159,12 @@ fun Context.getOpenTimerTabIntent(timerId: Int): PendingIntent {
return PendingIntent.getActivity(this, timerId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) 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 { fun Context.getAlarmIntent(alarm: Alarm): PendingIntent {
val intent = Intent(this, AlarmReceiver::class.java) val intent = Intent(this, AlarmReceiver::class.java)
intent.putExtra(ALARM_ID, alarm.id) intent.putExtra(ALARM_ID, alarm.id)

View File

@ -4,45 +4,25 @@ import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.Matrix import android.graphics.Matrix
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment 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.R
import com.simplemobiletools.clock.activities.SimpleActivity import com.simplemobiletools.clock.activities.SimpleActivity
import com.simplemobiletools.clock.adapters.StopwatchAdapter import com.simplemobiletools.clock.adapters.StopwatchAdapter
import com.simplemobiletools.clock.extensions.config
import com.simplemobiletools.clock.extensions.formatStopwatchTime import com.simplemobiletools.clock.extensions.formatStopwatchTime
import com.simplemobiletools.clock.helpers.SORT_BY_LAP import com.simplemobiletools.clock.helpers.SORT_BY_LAP
import com.simplemobiletools.clock.helpers.SORT_BY_LAP_TIME import com.simplemobiletools.clock.helpers.SORT_BY_LAP_TIME
import com.simplemobiletools.clock.helpers.SORT_BY_TOTAL_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.clock.models.Lap
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import kotlinx.android.synthetic.main.fragment_stopwatch.view.* import kotlinx.android.synthetic.main.fragment_stopwatch.view.*
class StopwatchFragment : Fragment() { 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 private var storedTextColor = 0
@ -51,6 +31,8 @@ class StopwatchFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
storeStateVariables() storeStateVariables()
val sorting = requireContext().config.stopwatchLapsSort
Lap.sorting = sorting
view = (inflater.inflate(R.layout.fragment_stopwatch, container, false) as ViewGroup).apply { view = (inflater.inflate(R.layout.fragment_stopwatch, container, false) as ViewGroup).apply {
stopwatch_time.setOnClickListener { stopwatch_time.setOnClickListener {
togglePlayPause() togglePlayPause()
@ -78,20 +60,7 @@ class StopwatchFragment : Fragment() {
stopwatch_lap.setOnClickListener { stopwatch_lap.setOnClickListener {
stopwatch_sorting_indicators_holder.beVisible() stopwatch_sorting_indicators_holder.beVisible()
if (laps.isEmpty()) { Stopwatch.lap()
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
updateLaps() updateLaps()
} }
@ -100,11 +69,10 @@ class StopwatchFragment : Fragment() {
changeSorting(it) changeSorting(it)
} }
} }
Lap.sorting = sorting
stopwatch_list.adapter = stopwatchAdapter stopwatch_list.adapter = stopwatchAdapter
} }
updateSortingIndicators() updateSortingIndicators(sorting)
return view return view
} }
@ -116,64 +84,25 @@ class StopwatchFragment : Fragment() {
if (storedTextColor != configTextColor) { if (storedTextColor != configTextColor) {
stopwatchAdapter.updateTextColor(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() { override fun onPause() {
super.onPause() super.onPause()
storeStateVariables() storeStateVariables()
} Stopwatch.removeUpdateListener(updateListener)
override fun onDestroy() {
super.onDestroy()
if (isRunning && activity?.isChangingConfigurations == false) {
context?.toast(R.string.stopwatch_stopped)
}
isRunning = false
updateHandler.removeCallbacks(updateRunnable)
} }
private fun storeStateVariables() { private fun storeStateVariables() {
storedTextColor = requireContext().getProperTextColor() 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() { private fun setupViews() {
val properPrimaryColor = requireContext().getProperPrimaryColor() val properPrimaryColor = requireContext().getProperPrimaryColor()
view.apply { view.apply {
@ -181,60 +110,30 @@ class StopwatchFragment : Fragment() {
stopwatch_play_pause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, properPrimaryColor) stopwatch_play_pause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, properPrimaryColor)
stopwatch_reset.applyColorFilter(requireContext().getProperTextColor()) stopwatch_reset.applyColorFilter(requireContext().getProperTextColor())
} }
updateIcons()
updateDisplayedText()
} }
private fun updateIcons() { private fun updateIcons(state: Stopwatch.State) {
val drawableId = if (isRunning) R.drawable.ic_pause_vector else R.drawable.ic_play_vector 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 val iconColor = if (requireContext().getProperPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE
view.stopwatch_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor)) view.stopwatch_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
} }
private fun togglePlayPause() { private fun togglePlayPause() {
isRunning = !isRunning Stopwatch.toggle(true)
updateStopwatchState(true)
} }
private fun updateStopwatchState(setUptimeAtStart: Boolean) {
updateIcons()
view.stopwatch_lap.beVisibleIf(isRunning)
if (isRunning) { private fun updateDisplayedText(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
updateHandler.post(updateRunnable) view.stopwatch_time.text = totalTime.formatStopwatchTime(useLongerMSFormat)
view.stopwatch_reset.beVisible() if (Stopwatch.laps.isNotEmpty() && lapTime != -1L) {
if (setUptimeAtStart) { stopwatchAdapter.updateLastField(lapTime, totalTime)
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 resetStopwatch() { private fun resetStopwatch() {
updateHandler.removeCallbacksAndMessages(null) Stopwatch.reset()
isRunning = false
currentTicks = 0
totalTicks = 0
currentLap = 1
lapTicks = 0
laps.clear()
updateIcons()
stopwatchAdapter.updateItems(laps)
updateLaps()
view.apply { view.apply {
stopwatch_reset.beGone() stopwatch_reset.beGone()
stopwatch_lap.beGone() stopwatch_lap.beGone()
@ -244,21 +143,22 @@ class StopwatchFragment : Fragment() {
} }
private fun changeSorting(clickedValue: Int) { private fun changeSorting(clickedValue: Int) {
sorting = if (sorting and clickedValue != 0) { val sorting = if (Lap.sorting and clickedValue != 0) {
sorting.flipBit(SORT_DESCENDING) Lap.sorting.flipBit(SORT_DESCENDING)
} else { } else {
clickedValue or SORT_DESCENDING clickedValue or SORT_DESCENDING
} }
updateSorting() updateSorting(sorting)
} }
private fun updateSorting() { private fun updateSorting(sorting: Int) {
updateSortingIndicators() updateSortingIndicators(sorting)
Lap.sorting = sorting Lap.sorting = sorting
requireContext().config.stopwatchLapsSort = sorting
updateLaps() updateLaps()
} }
private fun updateSortingIndicators() { private fun updateSortingIndicators(sorting: Int) {
var bitmap = requireContext().resources.getColoredBitmap(R.drawable.ic_sorting_triangle_vector, requireContext().getProperPrimaryColor()) var bitmap = requireContext().resources.getColoredBitmap(R.drawable.ic_sorting_triangle_vector, requireContext().getProperPrimaryColor())
view.apply { view.apply {
stopwatch_sorting_indicator_1.beInvisibleIf(sorting and SORT_BY_LAP == 0) stopwatch_sorting_indicator_1.beInvisibleIf(sorting and SORT_BY_LAP == 0)
@ -281,20 +181,18 @@ class StopwatchFragment : Fragment() {
} }
private fun updateLaps() { private fun updateLaps() {
stopwatchAdapter.updateItems(laps) stopwatchAdapter.updateItems(Stopwatch.laps)
} }
private val updateRunnable = object : Runnable { private val updateListener = object : Stopwatch.UpdateListener {
override fun run() { override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
if (isRunning) { updateDisplayedText(totalTime, lapTime, useLongerMSFormat)
if (totalTicks % 10 == 0) { }
updateDisplayedText()
} override fun onStateChanged(state: Stopwatch.State) {
totalTicks++ updateIcons(state)
currentTicks++ view.stopwatch_lap.beVisibleIf(state == Stopwatch.State.RUNNING)
lapTicks++ view.stopwatch_reset.beVisibleIf(state != Stopwatch.State.STOPPED)
updateHandler.postAtTime(this, uptimeAtStart + currentTicks * UPDATE_INTERVAL)
}
} }
} }
} }

View File

@ -10,6 +10,7 @@ import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.extensions.getDefaultAlarmSound import com.simplemobiletools.commons.extensions.getDefaultAlarmSound
import com.simplemobiletools.commons.extensions.getDefaultAlarmTitle import com.simplemobiletools.commons.extensions.getDefaultAlarmTitle
import com.simplemobiletools.commons.helpers.BaseConfig import com.simplemobiletools.commons.helpers.BaseConfig
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
class Config(context: Context) : BaseConfig(context) { class Config(context: Context) : BaseConfig(context) {
companion object { companion object {
@ -81,4 +82,8 @@ class Config(context: Context) : BaseConfig(context) {
var timerChannelId: String? var timerChannelId: String?
get() = prefs.getString(TIMER_CHANNEL_ID, null) get() = prefs.getString(TIMER_CHANNEL_ID, null)
set(id) = prefs.edit().putString(TIMER_CHANNEL_ID, id).apply() 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()
} }

View File

@ -22,6 +22,7 @@ const val ALARM_LAST_CONFIG = "alarm_last_config"
const val TIMER_LAST_CONFIG = "timer_last_config" const val TIMER_LAST_CONFIG = "timer_last_config"
const val INCREASE_VOLUME_GRADUALLY = "increase_volume_gradually" const val INCREASE_VOLUME_GRADUALLY = "increase_volume_gradually"
const val ALARMS_SORT_BY = "alarms_sort_by" 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 TABS_COUNT = 4
const val EDITED_TIME_ZONE_SEPARATOR = ":" 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 PICK_AUDIO_FILE_INTENT_ID = 9994
const val REMINDER_ACTIVITY_INTENT_ID = 9995 const val REMINDER_ACTIVITY_INTENT_ID = 9995
const val OPEN_ALARMS_TAB_INTENT_ID = 9996 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 UPDATE_WIDGET_INTENT_ID = 9997
const val OPEN_APP_INTENT_ID = 9998 const val OPEN_APP_INTENT_ID = 9998
const val ALARM_NOTIF_ID = 9998 const val ALARM_NOTIF_ID = 9998
const val TIMER_RUNNING_NOTIF_ID = 10000 const val TIMER_RUNNING_NOTIF_ID = 10000
const val STOPWATCH_RUNNING_NOTIF_ID = 10001
const val OPEN_TAB = "open_tab" const val OPEN_TAB = "open_tab"
const val TAB_CLOCK = 0 const val TAB_CLOCK = 0

View File

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

View File

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

View File

@ -6,6 +6,7 @@
<string name="label">Etiket</string> <string name="label">Etiket</string>
<string name="no_days_selected">Gün seçilməyib</string> <string name="no_days_selected">Gün seçilməyib</string>
<string name="timer">Sayğac</string> <string name="timer">Sayğac</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Dövrə</string> <string name="lap">Dövrə</string>
<string name="stopwatch_stopped">Saniyəölçən dayandı</string> <string name="stopwatch_stopped">Saniyəölçən dayandı</string>
<string name="timer_stopped">Sayğaç dayandı</string> <string name="timer_stopped">Sayğaç dayandı</string>

View File

@ -6,6 +6,7 @@
<string name="label">Štítek</string> <string name="label">Štítek</string>
<string name="no_days_selected">Nebyly označeny žádné dny</string> <string name="no_days_selected">Nebyly označeny žádné dny</string>
<string name="timer">Časovač</string> <string name="timer">Časovač</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Mezičas</string> <string name="lap">Mezičas</string>
<string name="stopwatch_stopped">Stopky byly zastaveny</string> <string name="stopwatch_stopped">Stopky byly zastaveny</string>
<string name="timer_stopped">Časovač byl zastaven</string> <string name="timer_stopped">Časovač byl zastaven</string>

View File

@ -6,6 +6,7 @@
<string name="label">Label</string> <string name="label">Label</string>
<string name="no_days_selected">Dim diwrnodau wedi\'u dewis</string> <string name="no_days_selected">Dim diwrnodau wedi\'u dewis</string>
<string name="timer">Amserydd</string> <string name="timer">Amserydd</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Lap</string> <string name="lap">Lap</string>
<string name="stopwatch_stopped">Cafodd y stopwats ei stopio</string> <string name="stopwatch_stopped">Cafodd y stopwats ei stopio</string>
<string name="timer_stopped">Cafodd yr amserydd ei stopio</string> <string name="timer_stopped">Cafodd yr amserydd ei stopio</string>

View File

@ -6,6 +6,7 @@
<string name="label">Etiket</string> <string name="label">Etiket</string>
<string name="no_days_selected">Der er ikke valgt nogen dage</string> <string name="no_days_selected">Der er ikke valgt nogen dage</string>
<string name="timer">Æggeur</string> <string name="timer">Æggeur</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Mellemtid</string> <string name="lap">Mellemtid</string>
<string name="stopwatch_stopped">Stopuret er standset</string> <string name="stopwatch_stopped">Stopuret er standset</string>
<string name="timer_stopped">Æggeuret er standset</string> <string name="timer_stopped">Æggeuret er standset</string>

View File

@ -7,6 +7,7 @@
<string name="label">Label</string> <string name="label">Label</string>
<string name="no_days_selected">Keine Tage ausgewählt</string> <string name="no_days_selected">Keine Tage ausgewählt</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Runde</string> <string name="lap">Runde</string>
<string name="stopwatch_stopped">Die Stoppuhr wurde angehalten</string> <string name="stopwatch_stopped">Die Stoppuhr wurde angehalten</string>
<string name="timer_stopped">Der Timer wurde angehalten</string> <string name="timer_stopped">Der Timer wurde angehalten</string>

View File

@ -7,6 +7,7 @@
<string name="label">Ετικέτα</string> <string name="label">Ετικέτα</string>
<string name="no_days_selected">Δεν έχουν επιλεγεί ημέρες</string> <string name="no_days_selected">Δεν έχουν επιλεγεί ημέρες</string>
<string name="timer">Χρονόμετρο</string> <string name="timer">Χρονόμετρο</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Γύρος</string> <string name="lap">Γύρος</string>
<string name="stopwatch_stopped">Ο χρονοδιακόπτης σταμάτησε</string> <string name="stopwatch_stopped">Ο χρονοδιακόπτης σταμάτησε</string>
<string name="timer_stopped">Το χρονόμετρο σταμάτησε</string> <string name="timer_stopped">Το χρονόμετρο σταμάτησε</string>

View File

@ -6,6 +6,7 @@
<string name="label">Etiqueta</string> <string name="label">Etiqueta</string>
<string name="no_days_selected">Ningún día seleccionado</string> <string name="no_days_selected">Ningún día seleccionado</string>
<string name="timer">Temporizador</string> <string name="timer">Temporizador</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Vuelta</string> <string name="lap">Vuelta</string>
<string name="stopwatch_stopped">El cronómetro s eha detenido</string> <string name="stopwatch_stopped">El cronómetro s eha detenido</string>
<string name="timer_stopped">El temporizador se ha detenido</string> <string name="timer_stopped">El temporizador se ha detenido</string>

View File

@ -7,6 +7,7 @@
<string name="label">Silt</string> <string name="label">Silt</string>
<string name="no_days_selected">Ühtegi päeva pole valitud</string> <string name="no_days_selected">Ühtegi päeva pole valitud</string>
<string name="timer">Taimer</string> <string name="timer">Taimer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Ringi aeg</string> <string name="lap">Ringi aeg</string>
<string name="stopwatch_stopped">Stopper on kinni</string> <string name="stopwatch_stopped">Stopper on kinni</string>
<string name="timer_stopped">Taimer on peatunud</string> <string name="timer_stopped">Taimer on peatunud</string>

View File

@ -6,6 +6,7 @@
<string name="label">Etiketa</string> <string name="label">Etiketa</string>
<string name="no_days_selected">Ez duzu egunik hautatu</string> <string name="no_days_selected">Ez duzu egunik hautatu</string>
<string name="timer">Tenporizagailua</string> <string name="timer">Tenporizagailua</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Bira</string> <string name="lap">Bira</string>
<string name="stopwatch_stopped">Kronometroa gelditu da</string> <string name="stopwatch_stopped">Kronometroa gelditu da</string>
<string name="timer_stopped">Tenporizagailua gelditu da</string> <string name="timer_stopped">Tenporizagailua gelditu da</string>

View File

@ -6,6 +6,7 @@
<string name="label">Tunniste</string> <string name="label">Tunniste</string>
<string name="no_days_selected">Ei päiviä valittuna</string> <string name="no_days_selected">Ei päiviä valittuna</string>
<string name="timer">Ajastin</string> <string name="timer">Ajastin</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Kierrosaika</string> <string name="lap">Kierrosaika</string>
<string name="stopwatch_stopped">Sekuntikello pysäytetty</string> <string name="stopwatch_stopped">Sekuntikello pysäytetty</string>
<string name="timer_stopped">Ajastin on pysäytetty</string> <string name="timer_stopped">Ajastin on pysäytetty</string>

View File

@ -7,6 +7,7 @@
<string name="label">Titre</string> <string name="label">Titre</string>
<string name="no_days_selected">Aucun jour sélectionné</string> <string name="no_days_selected">Aucun jour sélectionné</string>
<string name="timer">Minuterie</string> <string name="timer">Minuterie</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Tour</string> <string name="lap">Tour</string>
<string name="stopwatch_stopped">Chronomètre arrêté</string> <string name="stopwatch_stopped">Chronomètre arrêté</string>
<string name="timer_stopped">La minuterie a été arrêtée</string> <string name="timer_stopped">La minuterie a été arrêtée</string>

View File

@ -7,6 +7,7 @@
<string name="label">Etiqueta</string> <string name="label">Etiqueta</string>
<string name="no_days_selected">Ningún día seleccionado</string> <string name="no_days_selected">Ningún día seleccionado</string>
<string name="timer">Temporizador</string> <string name="timer">Temporizador</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Volta</string> <string name="lap">Volta</string>
<string name="stopwatch_stopped">Cronómetro parado</string> <string name="stopwatch_stopped">Cronómetro parado</string>
<string name="timer_stopped">Temporizador parado</string> <string name="timer_stopped">Temporizador parado</string>

View File

@ -6,6 +6,7 @@
<string name="label">Oznaka</string> <string name="label">Oznaka</string>
<string name="no_days_selected">Nije odabran nijedan dan</string> <string name="no_days_selected">Nije odabran nijedan dan</string>
<string name="timer">Brojač</string> <string name="timer">Brojač</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Krug</string> <string name="lap">Krug</string>
<string name="stopwatch_stopped">Štoperica je zaustavljena</string> <string name="stopwatch_stopped">Štoperica je zaustavljena</string>
<string name="timer_stopped">Brojač je zaustavljen</string> <string name="timer_stopped">Brojač je zaustavljen</string>

View File

@ -6,6 +6,7 @@
<string name="label">Címke</string> <string name="label">Címke</string>
<string name="no_days_selected">Nincs nap kiválasztva</string> <string name="no_days_selected">Nincs nap kiválasztva</string>
<string name="timer">Időzítő</string> <string name="timer">Időzítő</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Kör</string> <string name="lap">Kör</string>
<string name="stopwatch_stopped">A stopper leállt</string> <string name="stopwatch_stopped">A stopper leállt</string>
<string name="timer_stopped">Az időzítő leállt</string> <string name="timer_stopped">Az időzítő leállt</string>

View File

@ -6,6 +6,7 @@
<string name="label">Label</string> <string name="label">Label</string>
<string name="no_days_selected">Tidak ada hari yang dipilih</string> <string name="no_days_selected">Tidak ada hari yang dipilih</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Putaran</string> <string name="lap">Putaran</string>
<string name="stopwatch_stopped">Stopwatch telah berhenti</string> <string name="stopwatch_stopped">Stopwatch telah berhenti</string>
<string name="timer_stopped">Timer telah berhenti</string> <string name="timer_stopped">Timer telah berhenti</string>

View File

@ -7,6 +7,7 @@
<string name="label">Etichetta</string> <string name="label">Etichetta</string>
<string name="no_days_selected">Nessun giorno selezionato</string> <string name="no_days_selected">Nessun giorno selezionato</string>
<string name="timer">Contaminuti</string> <string name="timer">Contaminuti</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Parziale</string> <string name="lap">Parziale</string>
<string name="stopwatch_stopped">Il cronometro è stato fermato</string> <string name="stopwatch_stopped">Il cronometro è stato fermato</string>
<string name="timer_stopped">Il contaminuti è stato fermato</string> <string name="timer_stopped">Il contaminuti è stato fermato</string>

View File

@ -6,6 +6,7 @@
<string name="label">ラベル</string> <string name="label">ラベル</string>
<string name="no_days_selected">曜日が選択されていません</string> <string name="no_days_selected">曜日が選択されていません</string>
<string name="timer">タイマー</string> <string name="timer">タイマー</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">ラップ</string> <string name="lap">ラップ</string>
<string name="stopwatch_stopped">ストップウォッチが停止しました</string> <string name="stopwatch_stopped">ストップウォッチが停止しました</string>
<string name="timer_stopped">タイマーが停止しました</string> <string name="timer_stopped">タイマーが停止しました</string>

View File

@ -6,6 +6,7 @@
<string name="label">Etiketė</string> <string name="label">Etiketė</string>
<string name="no_days_selected">Nepasirinkta nė vienos dienos</string> <string name="no_days_selected">Nepasirinkta nė vienos dienos</string>
<string name="timer">Laikmatis</string> <string name="timer">Laikmatis</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Etapas</string> <string name="lap">Etapas</string>
<string name="stopwatch_stopped">Chronometras buvo sustabdytas</string> <string name="stopwatch_stopped">Chronometras buvo sustabdytas</string>
<string name="timer_stopped">Laikmatis buvo sustabdytas</string> <string name="timer_stopped">Laikmatis buvo sustabdytas</string>

View File

@ -6,6 +6,7 @@
<string name="label">അടിക്കുറിപ്പ് </string> <string name="label">അടിക്കുറിപ്പ് </string>
<string name="no_days_selected">ദിവസങ്ങളൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല</string> <string name="no_days_selected">ദിവസങ്ങളൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല</string>
<string name="timer">ടൈമർ</string> <string name="timer">ടൈമർ</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">ലാപ്‌</string> <string name="lap">ലാപ്‌</string>
<string name="stopwatch_stopped">Stopwatch has been stopped</string> <string name="stopwatch_stopped">Stopwatch has been stopped</string>
<string name="timer_stopped">സ്റ്റോപ്പ് വാച്ച് നിർത്തി</string> <string name="timer_stopped">സ്റ്റോപ്പ് വാച്ച് നിർത്തി</string>

View File

@ -6,6 +6,7 @@
<string name="label">Påskrift</string> <string name="label">Påskrift</string>
<string name="no_days_selected">Ingen dager valgte</string> <string name="no_days_selected">Ingen dager valgte</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Runde</string> <string name="lap">Runde</string>
<string name="stopwatch_stopped">Stoppeklokke er stoppet</string> <string name="stopwatch_stopped">Stoppeklokke er stoppet</string>
<string name="timer_stopped">Timer er stoppet</string> <string name="timer_stopped">Timer er stoppet</string>

View File

@ -6,6 +6,7 @@
<string name="label">Label</string> <string name="label">Label</string>
<string name="no_days_selected">Geen dagen geselecteerd</string> <string name="no_days_selected">Geen dagen geselecteerd</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Ronde</string> <string name="lap">Ronde</string>
<string name="stopwatch_stopped">Stopwatch is gestopt</string> <string name="stopwatch_stopped">Stopwatch is gestopt</string>
<string name="timer_stopped">Timer is gestopt</string> <string name="timer_stopped">Timer is gestopt</string>

View File

@ -7,6 +7,7 @@
<string name="label">Etykieta</string> <string name="label">Etykieta</string>
<string name="no_days_selected">Nie wybrano dni</string> <string name="no_days_selected">Nie wybrano dni</string>
<string name="timer">Minutnik</string> <string name="timer">Minutnik</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Okrążenie</string> <string name="lap">Okrążenie</string>
<string name="stopwatch_stopped">Stoper został zatrzymany</string> <string name="stopwatch_stopped">Stoper został zatrzymany</string>
<string name="timer_stopped">Minutnik został zatrzymany</string> <string name="timer_stopped">Minutnik został zatrzymany</string>

View File

@ -6,6 +6,7 @@
<string name="label">Legenda</string> <string name="label">Legenda</string>
<string name="no_days_selected">Nenhum dia selecionado</string> <string name="no_days_selected">Nenhum dia selecionado</string>
<string name="timer">Temporizador</string> <string name="timer">Temporizador</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Volta</string> <string name="lap">Volta</string>
<string name="stopwatch_stopped">Cronómetro parado</string> <string name="stopwatch_stopped">Cronómetro parado</string>
<string name="timer_stopped">Temporizador parado</string> <string name="timer_stopped">Temporizador parado</string>

View File

@ -6,6 +6,7 @@
<string name="label">Etichetă</string> <string name="label">Etichetă</string>
<string name="no_days_selected">Nici o zi selectată</string> <string name="no_days_selected">Nici o zi selectată</string>
<string name="timer">Temporizator</string> <string name="timer">Temporizator</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">O tură</string> <string name="lap">O tură</string>
<string name="stopwatch_stopped">Cronometrul a fost oprit</string> <string name="stopwatch_stopped">Cronometrul a fost oprit</string>
<string name="timer_stopped">Temporizatorul a fost oprit</string> <string name="timer_stopped">Temporizatorul a fost oprit</string>

View File

@ -7,6 +7,7 @@
<string name="label">Метка</string> <string name="label">Метка</string>
<string name="no_days_selected">Дни не выбраны</string> <string name="no_days_selected">Дни не выбраны</string>
<string name="timer">Таймер</string> <string name="timer">Таймер</string>
<string name="stopwatch">Секундомер</string>
<string name="lap">Круг</string> <string name="lap">Круг</string>
<string name="stopwatch_stopped">Секундомер остановлен</string> <string name="stopwatch_stopped">Секундомер остановлен</string>
<string name="timer_stopped">Таймер остановлен</string> <string name="timer_stopped">Таймер остановлен</string>

View File

@ -6,6 +6,7 @@
<string name="label">Štítok</string> <string name="label">Štítok</string>
<string name="no_days_selected">Neboli označené žiadne dni</string> <string name="no_days_selected">Neboli označené žiadne dni</string>
<string name="timer">Časovač</string> <string name="timer">Časovač</string>
<string name="stopwatch">Stopky</string>
<string name="lap">Okruh</string> <string name="lap">Okruh</string>
<string name="stopwatch_stopped">Stopky boli zastavené</string> <string name="stopwatch_stopped">Stopky boli zastavené</string>
<string name="timer_stopped">Časovač bol zastavený</string> <string name="timer_stopped">Časovač bol zastavený</string>

View File

@ -7,6 +7,7 @@
<string name="label">Etikett</string> <string name="label">Etikett</string>
<string name="no_days_selected">Inga dagar har valts</string> <string name="no_days_selected">Inga dagar har valts</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Varv</string> <string name="lap">Varv</string>
<string name="stopwatch_stopped">Stoppuret har stoppats</string> <string name="stopwatch_stopped">Stoppuret har stoppats</string>
<string name="timer_stopped">Timern har stoppats</string> <string name="timer_stopped">Timern har stoppats</string>

View File

@ -7,6 +7,7 @@
<string name="label">Etiket</string> <string name="label">Etiket</string>
<string name="no_days_selected">Hiçbir gün seçilmedi</string> <string name="no_days_selected">Hiçbir gün seçilmedi</string>
<string name="timer">Zamanlayıcı</string> <string name="timer">Zamanlayıcı</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Tur</string> <string name="lap">Tur</string>
<string name="stopwatch_stopped">Kronometre durduruldu</string> <string name="stopwatch_stopped">Kronometre durduruldu</string>
<string name="timer_stopped">Zamanlayıcı durduruldu</string> <string name="timer_stopped">Zamanlayıcı durduruldu</string>

View File

@ -6,6 +6,7 @@
<string name="label">Мітка</string> <string name="label">Мітка</string>
<string name="no_days_selected">Дні не обрано</string> <string name="no_days_selected">Дні не обрано</string>
<string name="timer">Таймер</string> <string name="timer">Таймер</string>
<string name="stopwatch">Секундомір</string>
<string name="lap">Інтервал</string> <string name="lap">Інтервал</string>
<string name="stopwatch_stopped">Секундомір зупинено</string> <string name="stopwatch_stopped">Секундомір зупинено</string>
<string name="timer_stopped">Таймер зупинено</string> <string name="timer_stopped">Таймер зупинено</string>

View File

@ -7,6 +7,7 @@
<string name="label">标签</string> <string name="label">标签</string>
<string name="no_days_selected">未选择哪一天</string> <string name="no_days_selected">未选择哪一天</string>
<string name="timer">定时器</string> <string name="timer">定时器</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">分段</string> <string name="lap">分段</string>
<string name="stopwatch_stopped">秒表已停止</string> <string name="stopwatch_stopped">秒表已停止</string>
<string name="timer_stopped">定时器已停止</string> <string name="timer_stopped">定时器已停止</string>

View File

@ -6,6 +6,7 @@
<string name="label">標籤</string> <string name="label">標籤</string>
<string name="no_days_selected">未選擇哪一天</string> <string name="no_days_selected">未選擇哪一天</string>
<string name="timer">計時器</string> <string name="timer">計時器</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">分段</string> <string name="lap">分段</string>
<string name="stopwatch_stopped">碼錶已停止</string> <string name="stopwatch_stopped">碼錶已停止</string>
<string name="timer_stopped">計時器已停止</string> <string name="timer_stopped">計時器已停止</string>

View File

@ -6,6 +6,7 @@
<string name="label">Label</string> <string name="label">Label</string>
<string name="no_days_selected">No days selected</string> <string name="no_days_selected">No days selected</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="stopwatch">Stopwatch</string>
<string name="lap">Lap</string> <string name="lap">Lap</string>
<string name="stopwatch_stopped">Stopwatch has been stopped</string> <string name="stopwatch_stopped">Stopwatch has been stopped</string>
<string name="timer_stopped">Timer has been stopped</string> <string name="timer_stopped">Timer has been stopped</string>