Skip to content

Add "Last heard" relative-time data point above map on QSO Details sheet#578

Open
patrickrb wants to merge 1 commit into
devfrom
optio/task-6e53a5ce-500a-463a-9c8e-eabc383f44cc
Open

Add "Last heard" relative-time data point above map on QSO Details sheet#578
patrickrb wants to merge 1 commit into
devfrom
optio/task-6e53a5ce-500a-463a-9c8e-eabc383f44cc

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

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.kt
    • New pure, unit-tested computeLastHeard(utcTimeMillis, nowMillis): LastHeard that buckets elapsed time into a LastHeard sealed type (Unknown/JustNow/Seconds/Minutes/Hours/Days). Kept Compose- and resource-free so it's directly testable and localizable.
    • New thin LastHeardRow composable that maps a bucket to a localized string via lastHeardText and renders the label/value row above QsoPathMap.
    • QsoSheetContent now observes the app's existing MainViewModel.timerSec (~1s UTC heartbeat) and passes it in, so the value refreshes while the sheet is open.
  • strings_compose.xml — new qso_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

  • Last heard label/value shown above the map.
  • ✅ Relative format with ago suffix.
  • ✅ Updates as time passes (driven by timerSec, the same clock the decode list uses).
  • ✅ Edge cases handled cleanly: missing/non-positive timestamp → --; future timestamp (clock skew) clamps to just now (never negative); very old → plain day count (e.g. 500d ago).

Notes / assumptions

  • Thresholds (5 s / 60 s / 60 m / 24 h) mirror the existing decode-row formatTimeAgo so recency reads consistently across the app.
  • Timestamp source is Ft8Message.utcTime (epoch ms); "now" is UtcTimer-based timerSec, both already used elsewhere.
  • New strings are English-only for now (other locales fall back to the default resource); no lint enforcement of MissingTranslation in 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

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

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 31.70732% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 22.44%. Comparing base (c2f63e8) to head (5665cb1).
⚠️ Report is 260 commits behind head on dev.

Files with missing lines Patch % Lines
...in/kotlin/radio/ks3ckc/ft8af/ui/decode/QsoSheet.kt 31.70% 28 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             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     
Flag Coverage Δ
android 14.67% <31.70%> (+1.53%) ⬆️
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...in/kotlin/radio/ks3ckc/ft8af/ui/decode/QsoSheet.kt 4.00% <31.70%> (+2.03%) ⬆️

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LastHeard sealed buckets + computeLastHeard(utcTimeMillis, nowMillis) for relative-time bucketing.
  • Renders a new LastHeardRow above QsoPathMap, refreshing using MainViewModel.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.

Comment on lines +115 to +116
// App-wide UTC clock (~1s heartbeat); keeps the "Last heard" value fresh.
val nowMillis by mainViewModel.timerSec.observeAsState(UtcTimer.getSystemTime())
Comment on lines +853 to +855
* 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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants