| Age | Commit message (Collapse) | Author |
|
Saved tracks browser:
- TrackDetailSheet converted from BottomSheetDialogFragment to Fragment so
it fills the overlay instead of floating over the map
- Adapter tap pushes detail fragment onto the back stack; back button returns
to the list naturally via popBackStack()
- SavedTracksFragment back button calls hideOverlays() (made internal) to
close the overlay and return to the map
- Layout updated to full-screen: header with back button, RecyclerView fills
remaining space via layout_weight instead of fixed 300dp
GPX import persistence:
- TrackRepository.importTrack() saves parsed points to Documents/Nav/ via
TrackStorage (URI is persisted to SharedPreferences) and adds to
in-memory list
- MainViewModel.addImportedTrack() now calls importTrack() so files opened
via the GPX handler load on future launches
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
- 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>
|
|
Layers menu:
- Add "Past tracks" switch to layer picker sheet — hides/shows previous
voyage polylines on the map, persisted across sessions via SharedPrefs
Vessel/crew registry:
- VesselRepository now takes a Context and persists all vessels and crew
to SharedPreferences as JSON (Moshi, same pattern as BoatProfileRepository)
- Wire VesselRepository as a singleton in NavApplication so the Fragment
and ViewModel share one instance — previously each created its own empty repo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replaces the nav_layers bottom nav item with a mini FloatingActionButton
anchored below the HUD, giving layers access without occupying a nav slot.
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
|
|
CI fixes:
- ParticleWindView: bounds.lonEast → bounds.northEast.longitude
(MapLibre 13.x API; lonEast not available as property)
- VesselRegistryFragment: Vessel(id="") and CrewMember(id="") →
include required name="" so copy() compiles
Hardware source feature flags:
- HardwareSource enum: AIS_RECEIVER, NMEA_INSTRUMENTS
- FeatureFlags: SharedPreferences-backed; all sources default OFF
(app works phone-only out of the box)
- NavApplication: exposes featureFlags singleton
- SafetyFragment: "HARDWARE DATA SOURCES" card with two MaterialSwitch
toggles; reads initial state from FeatureFlags; calls
SafetyListener.onHardwareSourceChanged on change
- AIS_RECEIVER flag:
- MainViewModel.processAisSentence() / refreshAisFromInternet() skip
when disabled; onHardwareSourceChanged() clears targets + cpaAlerts
- NMEA_INSTRUMENTS flag:
- LocationService skips nmeaStreamManager.start() on service launch
when disabled
- New ACTION_START_NMEA / ACTION_STOP_NMEA intents allow live toggle
- MainActivity.onHardwareSourceChanged() sends the appropriate intent
Tests: 102 total, all GREEN (+7 FeatureFlagsTest)
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
|
|
CPA/TCPA alerts:
- CpaAlert data class with severity (CAUTION/WARNING/DANGER) and label()
- CpaThresholds: 0.5/1.0/2.0 nm, max TCPA 30 min
- MainViewModel.updateCpaAlerts() runs on every GPS update and AIS
sentence; skips stationary contacts; filters TCPA ≤ 0 or > 30 min;
sorts by TCPA ascending
- SafetyFragment: new COLLISION AVOIDANCE card shows live alert list,
color-coded by severity; clears to "No traffic alerts" when safe
- MainActivity: observes cpaAlerts → safetyFragment.updateCpaAlerts()
Track stats:
- TrackStats data class: distanceNm, durationMs, avgSogKnots,
durationFormatted (m:ss / h:mm:ss)
- TrackRepository.haversineNm() + computeStats() using point timestamps
- MainViewModel exposes trackStats StateFlow, cleared on stopTrack()
- activity_main: dark pill overlay bar (tv_track_stats) above map
- MainActivity: shows "● REC 0.00nm m:ss x.xkt avg" while recording
Tests: 95 total, all GREEN (+16 new — 9 TrackStats, 7 CpaAlert)
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
|
|
NavLogger is a 200-entry in-memory ring buffer with format:
HH:mm:ss.SSS LEVEL tag: message
Entries are written at:
- App start
- First GPS fix (lat/lon/sog/cog)
- fetchCurrentConditions: request params + full success summary
(wind, temp, waves, swell, current) or exception class+message
- fetchForecastItems: request params + item count or error
DevLogSheet (BottomSheetDialogFragment) shows the log in a
monospace ScrollView with Copy and Clear buttons. Trigger: long-press
the drag handle bar on the instrument bottom sheet.
Primary use: copy the log and paste it to Claude to diagnose
conditions API failures without needing adb.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
GONE collapses the spinner's space, causing the value/unit TextViews to
reflow and bounce on every refresh. INVISIBLE keeps the 10dp width
reserved so the surrounding text never moves.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Day (light mode): vivid sky blue (#2B8FC4 → #87C8DF) fading into
tropical deep-sea blue (#0D7A9A → #074B68) — evokes a clear Kona
afternoon with deep Pacific below. Shimmer is bright white sunlight
on water (#50FFFFFF).
Night (dark mode): retains the existing dark charcoal sky (#1C1B1F →
#162433) and midnight navy sea (#0B3050 → #0D2137) with blue shimmer.
Colors are defined as adaptive color resources (values/ and values-night/)
and read in WaveView.onSizeChanged() and paint init instead of hardcoded
Color.parseColor() calls.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Rounded corners:
- Replace androidx CardView with MaterialCardView + ShapeAppearance.Nav.BottomSheet
so only the top-left and top-right corners are rounded (24dp); bottom corners
are square flush with the screen edge
Light/dark mode:
- Introduce values-night/colors.xml with the existing dark M3 palette (moved from
values/colors.xml); instrument values stay dark (#E6E1E5 text, #1C1B1F bg)
- values/colors.xml now carries the light M3 palette: surface #FFFBFE,
onSurface #1C1B1F, surfaceVariant #E7E0EB etc.; instrument text flips to dark
(#1C1B1F normal, #6F6878 secondary) for readability on light backgrounds
- Fix InstrumentUnit hardcoded #6B6070 → ?attr/colorOnSurfaceVariant so the
unit labels adapt automatically in both modes
- Mark the forecast row (ocean navy #0D2137) and WaveView with
android:forceDarkAllowed="false" so the system never inverts them — the
ocean section is intentionally always dark regardless of system theme
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- Add _conditionsLoading StateFlow to MainViewModel; set true when
loadConditions() fires, false when the fetch completes (success or fail)
- Cancel any in-flight conditions job when a new loadConditions() call
arrives (e.g. rapid map pans) so only the latest fetch drives the state
- Add a 10dp ProgressBar after each of the six data values in
layout_instruments_sheet.xml (wind, temp, baro, current, waves, swell);
visibility GONE by default, alpha 0.5; header section tinted
colorOnSurfaceVariant, forecast section tinted #5B9EC2 to match its labels
- Observe conditionsLoading in MainActivity and toggle all six spinners
together (all values come from one API call so they load in sync)
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- Strip all three unverified external link cards (ASA courses, ASA online,
Quizlet flashcards) from the Learn tab — asa.com is a Brazilian financial
site, not the American Sailing Association
- Remove dead openUrl() click handlers and unused Intent/Uri imports from LearnFragment
- Parallelize the two sequential API calls in fetchCurrentConditions so the HUD
populates in ~half the time (weather + marine fetched concurrently via async/await)
- Add onFailure logging in loadConditions so errors are visible in logcat instead
of silently swallowed
- Eagerly call loadConditions in the setStyle callback as a startup backstop: the
camera-idle listener fires before GPS fixes and the initial position may be (0,0),
so this guarantees a real fetch once the style is ready with a valid camera position
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
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
|
|
colorBackground is not a Material theme attribute; replace with
colorSurface which is the correct equivalent.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- BoatProfileRepository: Moshi+SharedPrefs persistence, pre-seeds
Erickson 23 (155%/100%/65% headsails, 2 reefs) and Cal 20 (1 reef)
- PreTripModels: expand with SailConfig, ConditionSlice, RouteProjection,
WatchItem, SimilarTripSummary; full BoatProfile with sail inventory
- PreTripReportGenerator: full rewrite — 4-hour conditions window,
Honokohau candidate-heading route projection with TWA scoring and
current-component calc, boat-specific sail plan from inventory,
hazard watchlist, similar-trip comparison via past GPX tracks
- PreTripReportViewModel: inject BoatProfileRepository, fix GPS to
LocationService.bestPosition (Honokohau fallback), pass full
forecastItems list and pastTracks to generator
- PreTripReportFragment: boat selector ChipGroup, auto-generate on open,
render all sections (conditions table, route, sail plan, watchlist,
similar trips)
- fragment_pretrip_report.xml: redesign with all report cards
- item_condition_column.xml: new layout for conditions-window columns
- NavApplication: instantiate BoatProfileRepository as app singleton
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- New UnitPrefs: persisted C/F, ft/m, hPa/inHg, kt/mph/kph — defaults to °F
- InstrumentHandler now accepts raw values (knots, metres, hPa, °C) and formats
via UnitPrefs on every update
- LayerPickerSheet expanded with wind particles toggle and unit chip selectors;
wrapped in ScrollView to handle the additional height
- MapLayerManager tracks particlesEnabled (persisted); setParticlesEnabled() added
- MainActivity: caches last raw values so refreshUnits() can reformat everything
instantly when the user changes a unit without waiting for new sensor data
- Recenter button moved to top (below HUD strip) so it's never obscured by the
bottom sheet or bottom nav
- Unit label TextViews given IDs in HUD and instrument sheet for live updates
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
|
|
Merged divergent branches (common ancestor a9d87b6). Took origin/main
as base (layer manager, recenter, quit button, GPX tracks, marine
conditions, dark theme, user position layer, active/past track layers)
and layered in particle wind simulation work:
- MapHandler: added setupWindLayer, updateWindLayer, updateWindGridLayer;
kept their user-pos layer, follow/recenter state, active+past tracks
- WeatherRepository: kept their fetchCurrentConditions, added fetchWindGrid
- MainViewModel: kept their track/marine/summary changes, added windGrid
StateFlow, loadWindGrid, buildGrid
- MainActivity: kept their full UI (layer picker, quit, recenter, fade
animations), added particleWindView wiring, weatherLoaded guard,
windArrow/windGrid observers
- activity_main.xml: took their LinearLayout redesign, added ParticleWindView
overlay inside ConstraintLayout
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- ParticleWindView: transparent View overlay on the map; tracks 300 particles
as three FloatArrays (lat, lon, age) — no per-frame allocation; projects
lat/lon → screen via MapLibreMap.projection each frame; draws a 14px tail
segment in the wind travel direction with alpha fading by age; respawns
out-of-bounds/aged particles randomly within visible bounds; speed scaled
to viewport so animation is consistent at any zoom level; animates via
postInvalidateOnAnimation()
- activity_main.xml: ParticleWindView added above MapView, match_parent,
non-interactive (clickable/focusable=false)
- MainActivity: finds particle_wind_view; calls attachMap after getMapAsync;
feeds directionDeg/speedKt from windArrow StateFlow
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
TrackStorage: openOutputStream null returned true (file never written).
Added IS_PENDING flag to fix Android 10-11 race where insert succeeds
but file isn't physically created yet. Added storage-mounted guard.
TrackRepository now logs save failures.
Stop tracking now requires a long press (haptic feedback) — prevents
accidental mid-sail stops from a single tap.
Quit button (top-right, tonal X) stops LocationService and calls
finishAffinity(). Prompts if a track is in progress.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
long-press picker
MapLayerManager: all raster sources registered at style-build time,
visibility toggled on demand. Persists base preset and wind state to
SharedPreferences. Sources: Google satellite, NOAA RNC charts
(tileservice.charts.noaa.gov), OWM wind, OpenSeaMap seamarks.
LayerPickerSheet: bottom sheet with chip group (Satellite/Charts/Hybrid)
and wind toggle, launched from map long-press.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
TrackSummarySheet: bottom sheet showing distance (nm), duration,
max/avg speed, avg wind and waves (when available, waves in ft).
Only shown for tracks ≥ 2 minutes — shorter tracks are discarded silently.
MainViewModel: exposes trackSummary SharedFlow (replay=0) and trackStartMs.
MainActivity: observes flow, shows sheet after stopTrack completes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
ocean forecast section
Full layout rewrite: 3×2 GridLayout instrument grid with inline
DirectionArrowView for AWS/TWS/HDG/COG, depth+baro row, animated
WaveView divider, and ocean-blue forecast section for Current/Waves/Swell.
Stubs valueCurrDir/valueWaveDir as nullable in InstrumentHandler
to compile; full handler rewrite follows in Task 6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|
|
- Fix compilation errors (missing imports for PropertyFactory and MaterialButton)
- Implement Pre-Trip Report with weather summary, boat profile, and sail suggestions
- Differentiate between active track (solid red) and past tracks (dotted red) on map
- Add navigation to Pre-Trip Report from Safety Dashboard
- Robustify track storage to preserve multiple tracks in session
Co-Authored-By: Gemini CLI <gemini-cli@google.com>
|
|
- Consolidate track data, weather, and log entries into a TripSummary
- Implement TripReportGenerator with Professional, Adventurous, Journal, and Pirate styles
- Add TripReportFragment and ViewModel for UI interaction
- Share TrackRepository and LogbookRepository via NavApplication singleton
- Fix compilation error in MainViewModel rich metadata recording
Co-Authored-By: Gemini CLI <gemini-cli@google.com>
|
|
- Add swell params to MarineApiService request
- Add swell fields to MarineHourly model
- Add MarineConditions snapshot model
- Add WeatherRepository.fetchCurrentConditions() (first forecast hour)
- Add MainViewModel.loadConditions() + marineConditions StateFlow
- Add Forecast section to instrument sheet: Curr / Wave / Swell
- Populate TWS from forecast wind speed on first GPS fix
- Trigger loadConditions() once on first GPS position received
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Drop VMG, Polar %, and PolarDiagramView — no NMEA source on boat
- Shrink grid to 3×2 (AWS/HDG/BSP / TWS/COG/SOG)
- Move Depth + Baro to expanded section side by side
- Initialize all instruments to "—" on startup
- Wire LocationService.locationFlow → SOG + COG display (real GPS)
- Wire LocationService.barometerStatus → Baro display (device sensor)
- Delete startInstrumentSimulation() fake loop entirely
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
immersive mode (#2)
* docs: add map interaction design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add map interaction implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(map): add isFollowing state and gesture-driven manual mode to MapHandler
* feat(ui): add fab_recenter pill button to map layout
* feat(map): wire UI fade-out and recenter button to MapHandler.isFollowing
* fix(map): prevent fadeIn flash on cold start; consolidate fab_mob listener
* fix(map): preserve user zoom level on recenter
Capture the current camera zoom when the user gestures (entering manual
mode) and pass it back to centerOnLocation in recenter(), so tapping
Recenter returns to the user's chosen zoom rather than always snapping
to the default 14.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(map): capture lastZoom on camera idle, not gesture start
OnCameraMoveStartedListener fires before the gesture completes, so
it captured the pre-gesture zoom. OnCameraIdleListener fires after
the camera settles, giving the user's final intended zoom level.
Only update lastZoom while in manual mode (isFollowing=false).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(map): guard recenter against null island and add KDoc
- Skip recenter() if no GPS fix received (lastLat/lastLon still 0.0)
to avoid animating to 0°N, 0°E
- Add KDoc comment to recenter() consistent with other public methods
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
The instrument sheet CardView has cardElevation=16dp which was
rendering on top of the FABs (default ~6dp elevation). Set
app:elevation=20dp on both FABs so they always appear above the sheet.
Also reverts oversized marginBottom back to standard 16dp all-round
now that elevation stacking is correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
anchorGravity=top centers the FAB on the sheet edge, leaving half
the button occluded. Increase marginBottom to 44dp (28dp to clear
the FAB radius + 16dp gap) so both buttons sit fully above the sheet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Both fab_mob and fab_record_track now anchor to instrument_bottom_sheet
so they sit above the sheet peek zone rather than being occluded by
the sheet's 16dp elevation. Resolves stash conflict in settings.local.json.
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>
|
|
- 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>
|
|
- 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>
|
|
Caused a fatal InflateException crash on app launch. Android requires
layout_width and layout_height on <include> tags explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Make InstrumentHandler.labelTrend and barometerTrendView nullable
- Remove anchorWatchHandler init block referencing non-existent view IDs
- Fix state.radiusM -> state.watchCircleRadiusMeters
- Add bottom_nav_weather_menu.xml with nav_forecast; point WeatherActivity at it
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>
|
|
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>
|
|
- Add VoiceLogFragment, VoiceLogViewModel, VoiceLogState, LogEntry, InMemoryLogbookRepository
- Wire voice log FAB in MainActivity to show/hide fragment container
- Add RECORD_AUDIO permission to manifest
- Add native CMakeLists.txt and native-lib.cpp stubs
- Fix missing BufferOverflow import in LocationService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
layout_bottom_toTopOf → layout_constraintBottom_toTopOf on fab_tidal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|