Skip to content

Commit 3563c3d

Browse files
authored
fix: rework how time is calculated in stopwatch (#211)
* fix: rework how time is calculated in stopwatch - Removed the tick logic to calculate time directly using `SystemClock.elapsedRealtime()` - Simplified live lap management See: #207 * fix: add `isActive` check
1 parent 2d6484b commit 3563c3d

File tree

4 files changed

+177
-105
lines changed

4 files changed

+177
-105
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Updated translations
13+
14+
### Fixed
15+
16+
- Fixed inaccuracy in stopwatch over long durations ([#207])
17+
1018
## [1.2.1] - 2025-05-08
1119

1220
### Changed
@@ -75,3 +83,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7583
[1.0.0]: https://github.com/FossifyOrg/Clock/releases/tag/1.0.0
7684

7785
[#158]: https://github.com/FossifyOrg/Clock/issues/158
86+
[#207]: https://github.com/FossifyOrg/Clock/issues/207

app/src/main/kotlin/org/fossify/clock/adapters/StopwatchAdapter.kt

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
44
import android.view.Menu
55
import android.view.View
66
import android.view.ViewGroup
7-
import android.widget.TextView
87
import org.fossify.clock.activities.SimpleActivity
98
import org.fossify.clock.databinding.ItemLapBinding
109
import org.fossify.clock.extensions.formatStopwatchTime
@@ -15,11 +14,12 @@ import org.fossify.clock.models.Lap
1514
import org.fossify.commons.adapters.MyRecyclerViewAdapter
1615
import org.fossify.commons.views.MyRecyclerView
1716

18-
class StopwatchAdapter(activity: SimpleActivity, var laps: ArrayList<Lap>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) :
19-
MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
20-
private var lastLapTimeView: TextView? = null
21-
private var lastTotalTimeView: TextView? = null
22-
private var lastLapId = 0
17+
class StopwatchAdapter(
18+
activity: SimpleActivity,
19+
var laps: ArrayList<Lap>,
20+
recyclerView: MyRecyclerView,
21+
itemClick: (Any) -> Unit,
22+
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
2323

2424
override fun getActionMenuId() = 0
2525

@@ -43,7 +43,7 @@ class StopwatchAdapter(activity: SimpleActivity, var laps: ArrayList<Lap>, recyc
4343
return createViewHolder(ItemLapBinding.inflate(layoutInflater, parent, false).root)
4444
}
4545

46-
override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
46+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
4747
val lap = laps[position]
4848
holder.bindView(lap, false, false) { itemView, layoutPosition ->
4949
setupView(itemView, lap)
@@ -55,18 +55,11 @@ class StopwatchAdapter(activity: SimpleActivity, var laps: ArrayList<Lap>, recyc
5555

5656
@SuppressLint("NotifyDataSetChanged")
5757
fun updateItems(newItems: ArrayList<Lap>) {
58-
lastLapId = 0
59-
laps = newItems.clone() as ArrayList<Lap>
60-
laps.sort()
58+
laps = newItems
6159
notifyDataSetChanged()
6260
finishActMode()
6361
}
6462

65-
fun updateLastField(lapTime: Long, totalTime: Long) {
66-
lastLapTimeView?.text = lapTime.formatStopwatchTime(false)
67-
lastTotalTimeView?.text = totalTime.formatStopwatchTime(false)
68-
}
69-
7063
private fun setupView(view: View, lap: Lap) {
7164
ItemLapBinding.bind(view).apply {
7265
lapOrder.text = lap.id.toString()
@@ -86,12 +79,6 @@ class StopwatchAdapter(activity: SimpleActivity, var laps: ArrayList<Lap>, recyc
8679
lapTotalTime.setOnClickListener {
8780
itemClick(SORT_BY_TOTAL_TIME)
8881
}
89-
90-
if (lap.id > lastLapId) {
91-
lastLapTimeView = lapLapTime
92-
lastTotalTimeView = lapTotalTime
93-
lastLapId = lap.id
94-
}
9582
}
9683
}
9784
}

app/src/main/kotlin/org/fossify/clock/fragments/StopwatchFragment.kt

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,35 @@ import org.fossify.clock.helpers.SORT_BY_TOTAL_TIME
2020
import org.fossify.clock.helpers.Stopwatch
2121
import org.fossify.clock.models.Lap
2222
import org.fossify.commons.dialogs.PermissionRequiredDialog
23-
import org.fossify.commons.extensions.*
23+
import org.fossify.commons.extensions.applyColorFilter
24+
import org.fossify.commons.extensions.beGone
25+
import org.fossify.commons.extensions.beInvisible
26+
import org.fossify.commons.extensions.beInvisibleIf
27+
import org.fossify.commons.extensions.beVisible
28+
import org.fossify.commons.extensions.beVisibleIf
29+
import org.fossify.commons.extensions.flipBit
30+
import org.fossify.commons.extensions.getColoredBitmap
31+
import org.fossify.commons.extensions.getColoredDrawableWithColor
32+
import org.fossify.commons.extensions.getProperBackgroundColor
33+
import org.fossify.commons.extensions.getProperPrimaryColor
34+
import org.fossify.commons.extensions.getProperTextColor
35+
import org.fossify.commons.extensions.openNotificationSettings
36+
import org.fossify.commons.extensions.updateTextColors
2437
import org.fossify.commons.helpers.SORT_DESCENDING
2538

2639
class StopwatchFragment : Fragment() {
2740

2841
lateinit var stopwatchAdapter: StopwatchAdapter
2942
private lateinit var binding: FragmentStopwatchBinding
3043

31-
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
44+
private var latestLapTime: Long = 0L
45+
private var latestTotalTime: Long = 0L
46+
47+
override fun onCreateView(
48+
inflater: LayoutInflater,
49+
container: ViewGroup?,
50+
savedInstanceState: Bundle?,
51+
): View {
3252
val sorting = requireContext().config.stopwatchLapsSort
3353
Lap.sorting = sorting
3454
binding = FragmentStopwatchBinding.inflate(inflater, container, false).apply {
@@ -62,7 +82,11 @@ class StopwatchFragment : Fragment() {
6282
updateLaps()
6383
}
6484

65-
stopwatchAdapter = StopwatchAdapter(activity as SimpleActivity, ArrayList(), stopwatchList) {
85+
stopwatchAdapter = StopwatchAdapter(
86+
activity = activity as SimpleActivity,
87+
laps = ArrayList(),
88+
recyclerView = stopwatchList
89+
) {
6690
if (it is Int) {
6791
changeSorting(it)
6892
}
@@ -77,7 +101,6 @@ class StopwatchFragment : Fragment() {
77101
override fun onResume() {
78102
super.onResume()
79103
setupViews()
80-
81104
Stopwatch.addUpdateListener(updateListener)
82105
updateLaps()
83106
binding.stopwatchSortingIndicatorsHolder.beVisibleIf(Stopwatch.laps.isNotEmpty())
@@ -100,22 +123,41 @@ class StopwatchFragment : Fragment() {
100123
val properPrimaryColor = requireContext().getProperPrimaryColor()
101124
binding.apply {
102125
requireContext().updateTextColors(stopwatchFragment)
103-
stopwatchPlayPause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, properPrimaryColor)
126+
stopwatchPlayPause.background = resources.getColoredDrawableWithColor(
127+
drawableId = R.drawable.circle_background_filled,
128+
color = properPrimaryColor
129+
)
104130
stopwatchReset.applyColorFilter(requireContext().getProperTextColor())
105131
}
106132
}
107133

108134
private fun updateIcons(state: Stopwatch.State) {
109135
val drawableId =
110-
if (state == Stopwatch.State.RUNNING) org.fossify.commons.R.drawable.ic_pause_vector else org.fossify.commons.R.drawable.ic_play_vector
111-
val iconColor = if (requireContext().getProperPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE
112-
binding.stopwatchPlayPause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
136+
if (state == Stopwatch.State.RUNNING) {
137+
org.fossify.commons.R.drawable.ic_pause_vector
138+
} else {
139+
org.fossify.commons.R.drawable.ic_play_vector
140+
}
141+
142+
val iconColor =
143+
if (requireContext().getProperPrimaryColor() == Color.WHITE) {
144+
Color.BLACK
145+
} else {
146+
Color.WHITE
147+
}
148+
149+
binding.stopwatchPlayPause.setImageDrawable(
150+
resources.getColoredDrawableWithColor(
151+
drawableId = drawableId,
152+
color = iconColor
153+
)
154+
)
113155
}
114156

115157
private fun togglePlayPause() {
116158
(activity as SimpleActivity).handleNotificationPermission { granted ->
117159
if (granted) {
118-
Stopwatch.toggle(true)
160+
Stopwatch.toggle()
119161
} else {
120162
PermissionRequiredDialog(
121163
activity as SimpleActivity,
@@ -125,15 +167,10 @@ class StopwatchFragment : Fragment() {
125167
}
126168
}
127169

128-
private fun updateDisplayedText(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
129-
binding.stopwatchTime.text = totalTime.formatStopwatchTime(useLongerMSFormat)
130-
if (Stopwatch.laps.isNotEmpty() && lapTime != -1L) {
131-
stopwatchAdapter.updateLastField(lapTime, totalTime)
132-
}
133-
}
134-
135170
private fun resetStopwatch() {
136171
Stopwatch.reset()
172+
latestLapTime = 0L
173+
latestTotalTime = 0L
137174

138175
updateLaps()
139176
binding.apply {
@@ -161,7 +198,10 @@ class StopwatchFragment : Fragment() {
161198
}
162199

163200
private fun updateSortingIndicators(sorting: Int) {
164-
var bitmap = requireContext().resources.getColoredBitmap(R.drawable.ic_sorting_triangle_vector, requireContext().getProperPrimaryColor())
201+
var bitmap = requireContext().resources.getColoredBitmap(
202+
resourceId = R.drawable.ic_sorting_triangle_vector,
203+
newColor = requireContext().getProperPrimaryColor()
204+
)
165205
binding.apply {
166206
stopwatchSortingIndicator1.beInvisibleIf(sorting and SORT_BY_LAP == 0)
167207
stopwatchSortingIndicator2.beInvisibleIf(sorting and SORT_BY_LAP_TIME == 0)
@@ -176,7 +216,8 @@ class StopwatchFragment : Fragment() {
176216
if (sorting and SORT_DESCENDING == 0) {
177217
val matrix = Matrix()
178218
matrix.postScale(1f, -1f)
179-
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
219+
bitmap =
220+
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
180221
}
181222
activeIndicator.setImageBitmap(bitmap)
182223
}
@@ -189,18 +230,31 @@ class StopwatchFragment : Fragment() {
189230
}
190231

191232
private fun updateLaps() {
233+
val allLaps = ArrayList(Stopwatch.laps)
234+
if (Stopwatch.laps.isNotEmpty() && Stopwatch.state != Stopwatch.State.STOPPED) {
235+
allLaps += Lap(
236+
id = Stopwatch.laps.size + 1,
237+
lapTime = latestLapTime,
238+
totalTime = latestTotalTime
239+
)
240+
}
241+
242+
allLaps.sort()
192243
stopwatchAdapter.apply {
193244
updatePrimaryColor()
194245
updateBackgroundColor(requireContext().getProperBackgroundColor())
195246
updateTextColor(requireContext().getProperTextColor())
196-
updateItems(Stopwatch.laps)
247+
updateItems(allLaps)
197248
}
198249
}
199250

200251
private val updateListener = object : Stopwatch.UpdateListener {
201252
override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
202253
activity?.runOnUiThread {
203-
updateDisplayedText(totalTime, lapTime, useLongerMSFormat)
254+
binding.stopwatchTime.text = totalTime.formatStopwatchTime(useLongerMSFormat)
255+
latestLapTime = lapTime
256+
latestTotalTime = totalTime
257+
updateLaps()
204258
}
205259
}
206260

0 commit comments

Comments
 (0)