| Age | Commit message (Collapse) | Author |
|
cleanup
- Layer picker: change android:src→android:background on preview ImageViews; shape
drawables have no intrinsic size so centerCrop renders blank — background fills correctly
- Replace ic_fish with ic_fishing_pole (rod + reel + line + hook vector)
- Move fab_fishing to CoordinatorLayout with layout_anchor on instrument_bottom_sheet
top|start, matching fab_record_track positioning so both FABs sit level at sheet top
- Raise fishing_overlay marginBottom 140dp→200dp to clear the realigned FAB
- Safety: remove HELP button + DocFragment import from SafetyFragment; rename Quit→Quit App
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Single-tap moon FAB (stacked below layers FAB) toggles red-on-black
night vision mode. NightVision theme now has a full M3 color palette
(surface, surfaceVariant, outline, secondary, error) all in dark red
tones. Screen brightness is set to 30% in night mode and restored to
system default on exit. State persists across sessions; recreate()
applies the theme switch cleanly.
https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
|
|
- TackDetector: sliding-window COG algorithm (60-140deg delta = tack/jibe)
with stability check to prevent mixed-window false positives; 13 tests green
- TackEvent, SavedTrack: new models wrapping points + summary + detected tacks
- TrackRepository.getSavedTracks(): returns List<SavedTrack> with lazy load
- MapHandler: zoomToTrackBounds (LatLngBounds fit with padding),
panToPoint (log step-through), updateTackLayer (yellow circle markers)
- MainViewModel: savedTracks + selectedTrack StateFlows, panToPosition SharedFlow
- SavedTracksFragment: RecyclerView list with date/distance/duration/tack count
- TrackDetailSheet: bottom sheet with stats + event log (tacks, speed changes,
start/end); tapping an entry pans the map to that position
- MainActivity: zoom map on track complete, wire Saved Tracks button in
instrument sheet, observe panToPosition to drive map, show tack markers
when a saved track is selected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Bottom nav: remove it from the following/panning fade cycle — only
fabRecenter and fabRecordTrack swap; nav stays visible throughout.
Drag handle: 4dp × 36dp was too small and competed with BottomSheet
drag gestures. Now 40dp tall via inset drawable (visual bar unchanged,
touch area is a usable 40dp × 36dp).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Brings in dev log (NavLogger), UnitPrefs, MapLayerManager, HUD views,
conditions throttling, track save/load pipeline, improved ParticleWindView
(antimeridian-aware, dynamic particle count), Snackbar error surfacing,
and all other main-branch work from the prior session.
Combined with master's hardware source flags, vessel registry, crew
management, thermal alarm, CPA collision alerts, and track stats.
Also documents primary branch policy in CLAUDE.md and .agent/config.md:
all work merges to main, never master.
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
|
|
- Vessel/CrewMember data classes with full profile fields
(MMSI, callsign, flag, home port, hull type, dimensions, roles)
- VesselRepository: in-memory CRUD for own vessel, fleet, and crew;
getOwnVessel(), getSkipper(), getCrewByRole() helpers
- VesselRegistryFragment: own-vessel card + crew list + known-vessels
list; AlertDialog editors for add/edit; long-press to delete
- ic_vessel.xml sailboat icon; 5th bottom-nav tab "Vessel"
- ThermalMonitor: thermalFlow() using Intent.ACTION_BATTERY_CHANGED
(battery °C) + PowerManager.OnThermalStatusChangedListener (API 29+);
ThermalState OK/WARM/HOT/CRITICAL with distinct-until-changed filter
- MainActivity: startThermalMonitoring() sounds mob_alarm + Toast on
HOT (≥45°C) and CRITICAL (≥50°C); auto-silences on OK/WARM
- MainViewModel: thermalState and thermalReading StateFlows
- 18 VesselRepositoryTest cases — all GREEN (test-runner)
- design.md: updated competitive matrix + feature implementation status
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
|
|
departure picker
Learn tab
- ic_open_in_new.xml: external link icon (box + arrow) applied to all browser-opening cards
- Migration guide cards retain internal › chevron; ASA/Flashcards cards show ↗ icon
- New REFERENCE section (offline, works without connectivity):
- ColRegs Rules of the Road → colregs_reference.md (rules 1–38, lights table,
sound signals, day shapes, memory aids)
- Sailing Quick Reference → sailing_reference.md (points of sail, Beaufort scale,
nav lights, knots, buoyage IALA-B, VHF channels, distress signals, tide rule of 12)
- ColRegs card moved from external ASA section to offline REFERENCE section
Log entry
- LogEntry: add photoPath field (absolute file path or content URI string)
- VoiceLogViewModel: replace confirmAndSave() with save(text, photoPath?) so the
fragment controls text (user may edit recognized speech before saving)
- VoiceLogFragment: redesigned layout with EditText (editable, voice fills it),
camera button (TakePicturePreview → JPEG in cacheDir), gallery button (GetContent),
photo thumbnail with remove button, Save / Clear row
- Manifest: add android.hardware.camera uses-feature (required=false)
Departure date/time picker (trip planning)
- ic_calendar.xml: calendar icon for the picker button
- PreTripReportViewModel: _departureMs StateFlow (default = now), setDeparture(ms)
- PreTripReportGenerator.generateReport(): departureDateTimeMs param;
findDepartureSlot() matches nearest UTC forecast item; condition window labels show
actual local times (e.g. "2 PM") when departure is not near-now; buildWatchList
uses departure hour for Kona trades warning instead of system clock
- fragment_pretrip_report.xml: DEPART card with label + calendar button above generate
- PreTripReportFragment: MaterialDatePicker (future dates only) → MaterialTimePicker
chain; auto-regenerates after picker confirms
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
UI cleanup:
- Remove always-visible MOB FAB — Safety screen button is sufficient
- Remove floating quit button; move to bottom of Safety screen
- fragment_container is now clickable/focusable, blocking map touch-through
for all overlays (trip reports, log, safety)
- Record Track FAB hidden while any overlay is shown, visible on map tab
New Learn tab (5th nav item):
- Migration guides for Navionics and Sea People (local markdown via DocFragment)
- ASA Course Catalog, ASA Online Learning, ColRegs, Sailing Flashcards
(open in browser)
- Remove blanket assets/ .gitignore so markdown docs are tracked
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- Persistent top-of-map HUD strip (SOG/COG/BSP/Depth) replaces instrument grid in bottom sheet
- Bottom sheet now shows area conditions only (Wind/Temp/Baro + wave forecast) — always visible
- loadConditions() fires on every camera idle event so panning the map refreshes conditions
- Crosshair appears at map center while panning; hides when following GPS
- Added tempC field to MarineConditions (already fetched from Open-Meteo hourly)
- InstrumentHandler slimmed to area conditions only; updateDisplay() removed
- LocationService pipes nmeaBoatSpeedData from NmeaStreamManager to companion flow
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Layers acts as an action button — shows LayerPickerSheet and snaps
back to Map so it never stays selected. Instruments tab removed;
sheet expand/collapse via swipe as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
* Add GpsPosition data class and NMEA RMC parser with tests
- GpsPosition: latitude, longitude, sog (knots), cog (degrees true), timestampMs
- NmeaParser.parseRmc: handles GP/GN talker IDs, void status, malformed input
- SOG/COG default to 0.0 when fields absent; S/W coords are negative
- 13 unit tests: GpsPositionTest (2), NmeaParserTest (11) — all GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add harmonic tide height predictions (Section 3.2 / 4.2)
Implement offline harmonic tide prediction as specified in COMPONENT_DESIGN.md:
- TideConstituent: name, speedDegPerHour, amplitudeMeters, phaseDeg
- TidePrediction: timestampMs, heightMeters
- TideStation: id, name, lat, lon, datumOffsetMeters, constituents
- HarmonicTideCalculator: predictHeight(), predictRange(), findHighLow()
Formula: h(t) = Z0 + Σ [ Hi × cos( ωi × (t − t0) − φi ) ]
- 15 unit tests covering all calculation paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement isochrone-based weather routing (Section 3.4)
* feat: implement PDF logbook export (Section 4.8)
- LogbookEntry data class: timestampMs, lat/lon, SOG, COG, wind, baro, depth, event/notes
- LogbookFormatter: UTC time, position (deg/dec-min), 16-pt compass, row/page builders
- LogbookPdfExporter: landscape A4 PDF via android.graphics.pdf.PdfDocument with column headers,
alternating row shading, and table border
- 20 unit tests covering all formatting helpers and data model behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: offline GRIB staleness checker, ViewModel integration, and UI badge
- Add GribRegion, GribFile data models and GribFileManager interface
- Add InMemoryGribFileManager for testing and default use
- Add GribStalenessChecker with FreshnessResult sealed class (Fresh/Stale/NoData)
- Integrate weatherStaleness StateFlow into MainViewModel (checked after loadWeather)
- Add yellow staleness banner TextView to fragment_map.xml
- Wire staleness banner in MapFragment (shown on Stale, hidden on Fresh/NoData)
- Add GribStalenessCheckerTest (4 TDD tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: satellite GRIB download with bandwidth optimisation (§9.1)
Implements weather data download over Iridium satellite links:
- GribParameter enum with SATELLITE_MINIMAL set (wind + pressure only)
- SatelliteDownloadRequest data class (region, params, forecast hours, resolution)
- SatelliteGribDownloader: size/time estimation, abort-on-oversized, pluggable fetcher
- 8 unit tests covering estimation scaling, minimal param set, and download outcomes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add AnchorWatchHandler UI with Depth/Rode Out inputs and suggested radius
- Add AnchorWatchState with calculateRecommendedWatchCircleRadius, which
uses ScopeCalculator.watchCircleRadius (Pythagorean scope formula) and
falls back to rode length when geometry is invalid
- Add AnchorWatchHandler Fragment with EditText inputs for Depth (m) and
Rode Out (m); updates suggested watch circle radius live via TextWatcher
- Add fragment_anchor_watch.xml layout
- Wire AnchorWatchHandler into bottom navigation (MainActivity + menu)
- Add AnchorWatchStateTest covering valid geometry, short-rode fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(safety): log wind and current conditions at MOB activation (Section 4.6)
Per COMPONENT_DESIGN.md Section 4.6, the MOB navigation view must display
wind and current conditions at the time of the event.
- MobEvent: add nullable windSpeedKt, windDirectionDeg, currentSpeedKt,
currentDirectionDeg fields captured at the exact moment of activation
- MobAlarmManager.activate(): accept optional wind/current params and
forward them into MobEvent (defaults to null for backward compatibility)
- LocationService (new): aggregates live SensorData (resolves true wind via
TrueWindCalculator) and marine-forecast current conditions; snapshot()
provides a point-in-time EnvironmentalSnapshot for safety-critical logging
- MobAlarmManagerTest: add tests for wind/current storage and null defaults
- LocationServiceTest (new): covers snapshot, true-wind resolution,
current-condition updates, and the latestSensor flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(gps): implement NMEA/Android GPS sensor fusion in LocationService
Adds priority-based selection between NMEA GPS (dedicated marine GPS,
higher priority) and Android system GPS (fallback) within LocationService.
Selection policy:
1. Prefer NMEA when its most recent fix is fresh (≤ nmeaStalenessThresholdMs, default 5 s)
2. Fall back to Android GPS when NMEA is stale
3. Use stale NMEA only when Android has never reported a fix
4. bestPosition is null until at least one source reports
New public API:
- GpsSource enum (NONE, NMEA, ANDROID)
- LocationService.updateNmeaGps(GpsPosition)
- LocationService.updateAndroidGps(GpsPosition)
- LocationService.bestPosition: StateFlow<GpsPosition?>
- LocationService.activeGpsSource: StateFlow<GpsSource>
- Injectable clockMs parameter for deterministic unit tests
Adds 7 unit tests covering: no-data state, fresh NMEA priority,
stale NMEA fallback, only-NMEA/only-Android scenarios, exact-threshold
edge case, and NMEA recovery after Android takeover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(gps): add fix-quality (accuracy) tier to GPS sensor fusion
Extend LocationService's source-selection policy with a quality-aware
"marginal staleness" zone between the primary and a new extended
staleness threshold (default 10 s):
1. Fresh NMEA (≤ primary threshold, 5 s) → always prefer NMEA
2. Marginally stale NMEA (5–10 s) → prefer NMEA only when
GpsPosition.accuracyMeters is strictly better than Android's;
fall back to Android conservatively when accuracy data is absent
3. Very stale NMEA (> 10 s) → always prefer Android
4. Only one source available → use it regardless of age
Changes:
- GpsPosition: add nullable accuracyMeters field (default null, no
breaking change to existing callers)
- LocationService: add nmeaExtendedThresholdMs constructor parameter;
recomputeBestPosition() now implements three-tier logic; extract
GpsPosition.hasStrictlyBetterAccuracyThan() helper
- LocationServiceTest: expose nmeaExtendedThresholdMs in fusionService
helper; add posWithAccuracy helper; add 4 new test cases covering
accuracy-based NMEA preference, worse-accuracy fallback, no-accuracy
conservative fallback, and very-stale unconditional fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add .gitignore, pull-crash-logs script, updated agent permissions
- .gitignore: exclude agent artifacts (.claudomator-env, .agent-home/, crash-logs/)
- scripts/pull-crash-logs: download Crashlytics crash reports via Firebase CLI
- .claude/settings.local.json: add Android SDK/emulator/adb permission rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update CI workflow to target main branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: resolve all Kotlin compilation errors blocking CI build
- Migrate kapt → KSP (Kotlin 2.0 + kapt is broken; KSP is the supported path)
- Fix duplicate onResume() override in MainActivity
- Fix wrong package imports: com.example.androidapp.data.model → org.terst.nav.data.model
across GribFileManager, GribStalenessChecker, SatelliteGribDownloader,
LogbookFormatter, LogbookPdfExporter, IsochroneRouter, AnchorWatchHandler
- Create missing SensorData, ApparentWind, TrueWindData, TrueWindCalculator classes
- Inline missing ScopeCalculator formula (Pythagorean) in AnchorWatchState
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(track): implement GPS track recording with map overlay
- TrackRepository + TrackPoint wired into MainViewModel:
isRecording/trackPoints StateFlows, startTrack/stopTrack/addGpsPoint
- MapHandler.updateTrackLayer(): lazily initialises a red LineLayer
and updates GeoJSON polyline from List<TrackPoint>
- fab_record_track FAB in activity_main.xml (top|end of bottom nav);
icon toggles between ic_track_record and ic_close while recording
- MainActivity feeds every GPS fix into viewModel.addGpsPoint() and
observes trackPoints to redraw the polyline in real time
- ic_track_record.xml vector drawable (red record dot)
- 8 TrackRepositoryTest tests all GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(ci): share APKs between jobs and expand smoke tests
CI — build job now uploads both APKs as the 'test-apks' artifact.
smoke-test job downloads them and passes -x assembleDebug
-x assembleDebugAndroidTest to skip recompilation (~4 min saved).
Test results uploaded as 'smoke-test-results' artifact on every run.
Smoke tests expanded from 1 → 11 tests covering:
- MainActivity launches without crash
- All 4 bottom-nav tabs are displayed
- Safety tab: Safety Dashboard, ACTIVATE MOB, ANCHOR WATCH visible
- Log tab: voice-log mic FAB visible
- Instruments tab: bottom sheet displayed
- Map tab: returns from overlay, mapView visible
- MOB FAB: always visible, visible on Safety tab
- Record Track FAB: displayed, toggles to Stop Recording, toggles back
MainActivity: moved isRecording observer to initializeUI() so the
FAB content description updates without requiring GPS permission
(needed for emulator tests that run without location permission).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: address simplify review findings
TrackRepository.addPoint() now returns Boolean (true if point was
added). MainViewModel.addGpsPoint() only updates _trackPoints StateFlow
when a point is actually appended — eliminates ~3,600 no-op flow
emissions per hour when not recording.
MainActivity: loadedStyle promoted from nullable field to
MutableStateFlow<Style?>; trackPoints observer uses filterNotNull +
combine so no track points are silently dropped if the style loads
after the first GPS fix.
Smoke tests: replaced 11× ActivityScenario.launch().use{} with a
single @get:Rule ActivityScenarioRule — same isolation, less
boilerplate.
CI: removed redundant app-debug artifact upload (app-debug.apk is
already included inside the test-apks artifact).
Removed stale/placeholder comments from MainActivity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): anchor record track FAB to instrument sheet to prevent it being hidden
The FAB was anchored to bottom_navigation, placing it in the peek zone
of the instrument bottom sheet (120dp) and making it invisible to users.
Re-anchoring to instrument_bottom_sheet keeps the button visible and
makes it track naturally with sheet drag gestures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): anchor MOB FAB to instrument sheet to match record-track FAB
The fab_mob was still anchored to bottom_navigation using the same
top|start pattern that caused fab_record_track to be hidden behind
the instrument sheet's 16dp elevation. Apply the same fix so the
safety-critical MOB button is not occluded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claudomator Agent <agent@claudomator>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Agent <agent@example.com>
Co-authored-by: Claude Agent <agent@claude.ai>
|
|
- TrackRepository + TrackPoint wired into MainViewModel:
isRecording/trackPoints StateFlows, startTrack/stopTrack/addGpsPoint
- MapHandler.updateTrackLayer(): lazily initialises a red LineLayer
and updates GeoJSON polyline from List<TrackPoint>
- fab_record_track FAB in activity_main.xml (top|end of bottom nav);
icon toggles between ic_track_record and ic_close while recording
- MainActivity feeds every GPS fix into viewModel.addGpsPoint() and
observes trackPoints to redraw the polyline in real time
- ic_track_record.xml vector drawable (red record dot)
- 8 TrackRepositoryTest tests all GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace FAB-based navigation with a 4-tab BottomNavigationView (Map,
Instruments, Log, Safety). Instruments use a collapsible bottom sheet;
Log and Safety display as full-screen overlay fragments.
- Add SafetyFragment with MOB and Anchor Watch controls
- Add DocFragment for in-app markdown help (Markwon: core, tables, images)
- Add layout_instruments_sheet with 3x3 instrument grid and PolarDiagramView
- Add fragment_safety and fragment_doc layouts
- Add vector drawables: ic_map, ic_instruments, ic_log, ic_safety, ic_close
- Update activity_main.xml to CoordinatorLayout with bottom sheet + overlay
- Fix: set isHideable=true before STATE_HIDDEN to avoid silent no-op from
behavior_hideable=false default; restore false on Map/Instruments tabs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- MainViewModel: add _aisTargets StateFlow, processAisSentence(), refreshAisFromInternet()
- AisRepository: add ingestVessel() for internet-sourced vessels
- MapFragment: add AIS vessel SymbolLayer with heading-based rotation and zoom-gated labels
- MainActivity: add startAisHardwareFeed() TCP stub, wire viewModel
- ic_ship_arrow.xml: new vector drawable for AIS target icons
- MainViewModelTest: 3 new AIS tests (processAisSentence happy path, dedup, non-AIS sentence)
- JVM test harness: /tmp/ais-vm-test-runner/ — 3 tests GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Merges wind/current overlay and weather forecast implementation:
- Weather feature: WeatherRepository, MainViewModel, MapFragment, ForecastFragment, ForecastAdapter
- Data models: WindArrow, ForecastItem, WeatherResponse, MarineResponse
- API services: WeatherApiService, MarineApiService (Open-Meteo, no key required)
- Layouts: activity_weather.xml, fragment_map.xml, fragment_forecast.xml, item_forecast.xml
- Resources: merged colors (wind_slow/medium/strong), strings, themes (Theme.NavApp added)
- Manifest: added ACCESS_COARSE_LOCATION
- build.gradle: merged deps — kept Firebase+MapLibre 11.5.1, added kotlin-kapt, retrofit, moshi, turbine
- Fix: re-packaged com.example.androidapp → org.terst.nav; weather MainActivity uses ActivityWeatherBinding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
App Distribution
|
|
Implements the wind/current map overlay and 7-day weather forecast
display that loads on application launch:
Data layer:
- Open-Meteo API (free, no key): WeatherApiService + MarineApiService
- Moshi-annotated response models (WeatherResponse, MarineResponse)
- WeatherRepository: parallel async fetch, Result<T> error propagation
- Domain models: WindArrow (with beaufortScale/isCalm) + ForecastItem
(with weatherDescription/isRainy via WMO weather codes)
Presentation layer:
- MainViewModel: StateFlow<UiState> (Loading/Success/Error),
windArrow and forecast streams
- MainActivity: runtime location permission, FusedLocationProvider
GPS fetch on startup, falls back to SF Bay default
- MapFragment: MapLibre GL map with wind-arrow SymbolLayer;
icon rotated by wind direction, size scaled by speed in knots
- ForecastFragment + ForecastAdapter: RecyclerView ListAdapter
showing 7-day hourly forecast (time, description, wind, temp, precip)
Resources:
- ic_wind_arrow.xml vector drawable (north-pointing, rotated by MapLibre)
- BottomNavigationView: Map / Forecast tabs
- ViewBinding enabled throughout
Tests (TDD — written before implementation):
- WindArrowTest: calm detection, direction normalisation, Beaufort scale
- ForecastItemTest: weatherDescription, isRainy for WMO codes
- WeatherApiServiceTest: MockWebServer request params + response parsing
- WeatherRepositoryTest: MockK service mocks, data mapping, error paths
- MainViewModelTest: Turbine StateFlow assertions for all state transitions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|