Skip to content

Feature: Trip Mode — Mobile Operating Companion for Traveling Amateur Radio Operators #703

Description

@patrickrb

Overview

Trip Mode transforms FT8AF into a flagship companion for mobile amateur radio operators traveling by car, RV, train, or other vehicle. When active, Trip Mode continuously records the operator's GPS route, associates every QSO with its geographic coordinates, tracks vehicle speed and heading, detects U.S. state transitions, and surfaces real-time statistics throughout the journey. Completed trips are persisted and can be replayed on an interactive map.

This is intended as a production-quality, architecturally sound feature — not a prototype — built to be extensible for future capabilities such as RTOTA, county/interstate tracking, APRS integration, GPX/KML export, live sharing, and cloud sync.


Objectives

  • Record GPS location continuously throughout a trip
  • Associate every QSO logged during the trip with its GPS coordinates
  • Record vehicle speed and heading (when available from the GPS provider)
  • Detect and record U.S. state transitions
  • Record trip start/end timestamps
  • Calculate total miles traveled
  • Track distance between the vehicle and each contacted station
  • Calculate real-time statistics:
    • QSOs per hour
    • Miles per QSO
    • Longest QSO distance
    • Average QSO distance
  • Display all statistics live while the trip is in progress

User Experience

Entry Point

Add a Trip Mode entry in the main navigation drawer/menu.

Trip Start Flow

  1. On first launch of Trip Mode, request ACCESS_FINE_LOCATION (and ACCESS_BACKGROUND_LOCATION on Android 10+) if not already granted.
  2. On permission grant, start a background trip session via a foreground Service with a persistent notification.
  3. Display the live Trip Mode UI.

Trip Mode UI — Live Dashboard

Field Description
Trip Duration Elapsed time since trip start (HH:MM:SS)
GPS Coordinates Current latitude/longitude
Speed Current speed (MPH or KPH, user preference)
Heading Compass bearing with cardinal direction
Total Miles Odometer for the trip
Total QSOs Count of contacts logged during trip
Average QSO Distance Mean great-circle distance to all worked stations
Miles per QSO Total miles ÷ total QSOs
QSOs/Hour Rolling rate
Current State Detected U.S. state (or N/A outside USA)
Last Worked Station Callsign of most recent QSO
Last Worked Distance Distance to most recent QSO

The dashboard must survive screen rotation and handle Android lifecycle correctly. The underlying trip session must continue if the user navigates away from the Trip Mode screen.


Trip Replay Screen

A dedicated Trip Replay screen accessible from both the active trip and trip history.

Map Display

  • Plot the full GPS route as a polyline
  • Place a marker for every FT8 contact made during the trip
  • Draw lines connecting the vehicle's position at time-of-QSO to the contacted station's grid square center
  • Auto-zoom/pan to fit the entire trip on load

Contact Markers — Tap Detail

Tapping a contact marker opens a bottom sheet or dialog with:

  • Callsign
  • Grid square
  • Time of QSO
  • Distance (mi/km)
  • Band
  • Mode
  • Sent / received signal reports

Map Abstraction

Define a MapProvider interface so alternative map backends (OSMDroid, Mapbox, Google Maps, etc.) can be swapped in without changing the replay screen. The initial implementation should use one open/free provider suitable for distribution on F-Droid as well as Google Play.


Trip History

Persist completed trips to local storage (Room database).

Trip List Screen

Displays all saved trips with:

  • Date (and day of week)
  • Duration
  • Distance traveled
  • Number of QSOs
  • States visited (comma-separated or badge list)
  • Average contact distance

Tapping any row reopens that trip in the Trip Replay screen.


Architecture

Follow the existing FT8AF layered architecture. Do not place business logic inside Activities or Fragments.

Proposed Package Structure

com.ft8af.trip
├── data
│   ├── db/           # Room entities, DAOs, TripDatabase
│   ├── model/        # TripSession, TripQso, TripPoint, TripSummary
│   └── repository/   # TripRepository
├── location
│   ├── LocationProvider.kt (interface)
│   ├── FusedLocationProvider.kt
│   └── GpsLocationProvider.kt (fallback)
├── service
│   └── TripRecordingService.kt  # Foreground service
├── stats
│   ├── TripStatistics.kt
│   ├── QsoStatistics.kt
│   ├── LocationStatistics.kt
│   ├── DistanceCalculator.kt
│   ├── StateTracker.kt
│   └── TripRecorder.kt
├── map
│   ├── MapProvider.kt (interface)
│   ├── OsmMapProvider.kt
│   └── model/  # RouteOverlay, QsoMarker, ContactLine
├── ui
│   ├── dashboard/    # TripDashboardFragment + ViewModel
│   ├── replay/       # TripReplayFragment + ViewModel
│   └── history/      # TripHistoryFragment + ViewModel
└── di/               # Dependency injection bindings

Key Classes

Class Responsibility
TripRecordingService Foreground service; owns the GPS subscription and calls TripRecorder
TripRecorder Coordinates recording: receives location updates and QSO events, delegates to stats and repository
TripRepository Single source of truth; wraps Room DAOs and exposes Flow/LiveData
DistanceCalculator Haversine great-circle calculations; pure, stateless, fully testable
StateTracker Determines current U.S. state from a lat/lon; designed to be swappable (offline polygon lookup or reverse-geocode API)
TripStatistics Aggregate trip-level stats; operates on List<TripPoint> and List<TripQso>
QsoStatistics Per-QSO and cross-QSO distance/rate stats
LocationStatistics Speed/heading smoothing, odometer accumulation
MapProvider Interface abstracting all map SDK calls

Principles

  • Prefer constructor injection (Hilt or manual DI consistent with existing app)
  • Avoid static state and singletons where possible
  • All stats classes must be unit-testable without Android framework dependencies
  • StateTracker and DistanceCalculator should be pure Kotlin/Java

Data Model (Room)

TripSession   id, startTime, endTime, totalDistanceMiles, qsoCount, statesVisited, notes
TripPoint     id, tripId, timestamp, latitude, longitude, speedMph, headingDeg, accuracy
TripQso       id, tripId, timestamp, callsign, grid, band, mode, sentReport, rcvdReport,
              latitude, longitude, distanceMiles

Extension columns / tables can be added later for county, POTA reference, RTOTA segment, etc.


Performance & Battery

  • Use FusedLocationProviderClient with a LocationRequest tuned for driving speeds (e.g., 5-second interval, 10-meter displacement threshold)
  • Batch Room inserts; do not write every GPS point individually on the UI thread
  • Downsample GPS points for map rendering when point count exceeds a configurable threshold (default: 5,000 points)
  • Recover gracefully from process death: on service restart, reload the active trip from the database and continue appending
  • All database operations must be performed off the main thread

Extension Points for Future Features

Design these interfaces/hooks now, even if unimplemented:

Future Feature Extension Point
RTOTA TripSegment table + SegmentTracker interface
County tracking CountyTracker interface (similar to StateTracker)
Interstate tracking InterstateTracker interface
GPX / KML / GeoJSON export TripExporter interface with format-specific implementations
Live trip sharing TripBroadcaster interface
APRS integration AprsPositionReporter interface
POTA overlays PoiOverlayProvider interface in map abstraction
Replay animation TripAnimator in replay screen ViewModel
Cloud sync RemoteTripRepository implementing TripRepository
Public trip pages TripShareToken field on TripSession
Awards AwardEvaluator interface called post-trip

Implementation Plan

Phase 1 — Foundation

  • Define Room entities and DAOs (TripSession, TripPoint, TripQso)
  • Implement TripRepository
  • Implement DistanceCalculator with unit tests
  • Implement LocationStatistics (odometer, smoothing)
  • Implement StateTracker (offline or reverse-geocode)

Phase 2 — Recording Service

  • Implement TripRecordingService (foreground service, persistent notification)
  • Implement TripRecorder (wires GPS updates + QSO events → repository)
  • Hook QSO logging into TripRecorder (non-breaking)
  • Persist GPS points incrementally

Phase 3 — Live Dashboard UI

  • TripDashboardFragment + TripDashboardViewModel
  • Bind all statistics fields
  • Handle rotation / lifecycle
  • Add Trip Mode entry to main navigation

Phase 4 — Map & Replay

  • Define MapProvider interface
  • Implement OsmMapProvider
  • TripReplayFragment + TripReplayViewModel
  • Route polyline, QSO markers, contact lines
  • Contact tap → detail sheet

Phase 5 — Trip History

  • TripHistoryFragment + TripHistoryViewModel
  • Trip list with summary data
  • Navigate from list → replay

Phase 6 — Polish & Tests

  • Unit tests for all stats classes
  • Unit tests for DistanceCalculator and StateTracker
  • Integration test for TripRepository
  • UI consistency pass
  • Edge cases: no GPS fix, permission denied mid-trip, process death recovery
  • Final documentation pass

Acceptance Criteria

  • Trip Mode entry point visible in main navigation
  • Location permission requested gracefully; denial handled without crash
  • Trip recording continues when the screen is off or user navigates away
  • All dashboard fields update in real time
  • QSOs logged during a trip are persisted with GPS coordinates
  • State transitions detected and stored
  • Completed trip appears in Trip History list
  • Trip Replay map shows route, markers, and contact lines
  • Tapping a contact marker shows full QSO detail
  • App recovers correctly after process death mid-trip
  • No regressions in existing FT8/QSO logging functionality
  • All new stats classes have unit tests
  • No business logic in Activities or Fragments

Notes

  • Keep UI styling consistent with existing FT8AF screens (colors, typography, icon set)
  • The MapProvider abstraction should be forward-compatible with F-Droid distribution (no proprietary SDK hard-dependency)
  • Prefer Flow/LiveData from the repository layer consistent with existing patterns in FT8AF
  • Document all new public classes with KDoc/Javadoc

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions