Add "Last heard" relative-time data point above map on QSO Details sheet#578
Add "Last heard" relative-time data point above map on QSO Details sheet#578patrickrb wants to merge 1 commit into
Conversation
Show a human-readable recency indicator ("just now", "45s ago", "12m ago",
"3h ago", "2d ago") above the path map on the QSO Details bottom sheet, so
operators get immediate recency context without reading a raw timestamp.
The bucketing is extracted into a pure, unit-tested computeLastHeard()
returning a LastHeard sealed type (Compose-free, localizable), with a thin
LastHeardRow wrapper that maps buckets to localized strings. Edge cases are
handled cleanly: missing/non-positive timestamp -> "--", future timestamp
(clock skew) clamps to "just now", very old -> plain day count. The value
refreshes ~1s via the app's existing MainViewModel.timerSec UTC heartbeat
while the sheet is open. Thresholds mirror the decode-row "ago" formatter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #578 +/- ##
============================================
+ Coverage 21.66% 22.44% +0.77%
- Complexity 149 197 +48
============================================
Files 163 169 +6
Lines 20877 21672 +795
Branches 3128 3281 +153
============================================
+ Hits 4523 4864 +341
- Misses 16167 16585 +418
- Partials 187 223 +36
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a “Last heard” relative-time field to the QSO Details bottom sheet (above the path map), backed by a pure bucketing function (computeLastHeard) and localized strings, with unit tests covering bucket boundaries and edge cases.
Changes:
- Introduces
LastHeardsealed buckets +computeLastHeard(utcTimeMillis, nowMillis)for relative-time bucketing. - Renders a new
LastHeardRowaboveQsoPathMap, refreshing usingMainViewModel.timerSec. - Adds Compose string resources and new unit tests for bucket boundaries and edge cases.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/decode/QsoSheet.kt | Adds “Last heard” UI row and the pure bucketing/localization logic. |
| ft8af/app/src/main/res/values/strings_compose.xml | Adds localized strings for the new “Last heard” label and relative-time formats. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/decode/LastHeardTest.kt | Adds unit tests covering all bucket thresholds and edge cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // App-wide UTC clock (~1s heartbeat); keeps the "Last heard" value fresh. | ||
| val nowMillis by mainViewModel.timerSec.observeAsState(UtcTimer.getSystemTime()) |
| * Thresholds mirror the existing decode-row "ago" formatter (5 s / 60 s / 60 m / | ||
| * 24 h) so recency reads consistently across the app. | ||
| */ |
|
|
||
| @Test | ||
| fun `days bucket covers a day and beyond, including very old timestamps`() { | ||
| assertThat(computeLastHeard(now - 86_400_000L, now)).isEqualTo(LastHeard.Days(2 - 1)) |
Summary
Adds a Last heard data point to the QSO Details bottom sheet, rendered as a relative "ago" time (
just now,45s ago,12m ago,3h ago,2d ago) positioned above the map. Operators get immediate recency context without mentally computing elapsed time from a raw timestamp.What changed
QsoSheet.ktcomputeLastHeard(utcTimeMillis, nowMillis): LastHeardthat buckets elapsed time into aLastHeardsealed type (Unknown/JustNow/Seconds/Minutes/Hours/Days). Kept Compose- and resource-free so it's directly testable and localizable.LastHeardRowcomposable that maps a bucket to a localized string vialastHeardTextand renders the label/value row aboveQsoPathMap.QsoSheetContentnow observes the app's existingMainViewModel.timerSec(~1s UTC heartbeat) and passes it in, so the value refreshes while the sheet is open.strings_compose.xml— newqso_last_heard*strings (label +just now/%1$ds ago/%1$dm ago/%1$dh ago/%1$dd ago/--placeholder).LastHeardTest.kt— 7 tests covering every bucket boundary plus the edge cases.Acceptance criteria
agosuffix.timerSec, the same clock the decode list uses).--; future timestamp (clock skew) clamps tojust now(never negative); very old → plain day count (e.g.500d ago).Notes / assumptions
formatTimeAgoso recency reads consistently across the app.Ft8Message.utcTime(epoch ms); "now" isUtcTimer-basedtimerSec, both already used elsewhere.MissingTranslationin this module.Testing
./gradlew :app:testDebugUnitTest --tests '*.LastHeardTest' --tests '*.QsoSheetLogicTest'→ BUILD SUCCESSFUL (7/7 new tests pass; existing sheet tests unaffected). Main source compiles.🤖 Generated with Claude Code