| Age | Commit message (Collapse) | Author |
|
TrackStorage now writes to NavLogger at every decision point:
- Before MediaStore insert (shows the filename being written)
- Successful save: "saved nav_<ts>.gpx (N bytes) → <uri>"
- Every failure path: storage not mounted, insert null,
openOutputStream null, write exception, load parse error
- After loadViaMediaStore: count of tracks found
TrackRepository logs stopTrack summary (pts / duration / distance)
and explicitly logs when saveTrack returns false.
Long-press the drag handle after injecting a test track to see
exactly where the save pipeline breaks.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
test-apks: 1 day — only needed for the smoke-test job in the same run
smoke-test-results: 7 days — enough for post-failure debugging
Default is 90 days; at ~30 MB per run this was filling the 500 MB
free-tier limit in ~2 weeks.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
loadConditions now skips the API call if conditions were loaded
< 5 min ago and position hasn't moved > 2 nm — prevents 429 from
overlapping triggers (startup backstop, GPS first-fix, camera idle).
Dev log shows "conditions: skip age=Xs dist=Y.Znm" when throttled.
ParticleWindView starts hidden and is revealed only when the first
windArrow value arrives, so particles don't flash before the map loads.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
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
|
|
Adds _conditionsError SharedFlow to MainViewModel that emits the
exception class + message on fetchCurrentConditions failure.
MainActivity collects it and shows a LENGTH_LONG Snackbar — no adb
required to see why conditions aren't loading.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
The camera idle listener fires every time following-mode GPS moves the
map (~1 Hz). Each fire was calling loadConditions, which meant a new
API request (and spinner flash) every second. Fix: skip the camera idle
loadConditions call when isFollowing == true — the GPS first-fix handler
already owns that trigger. Camera idle only loads conditions when the
user has manually panned away from their position.
Also include exception class + message in the logcat tag so the actual
API failure reason is visible without --stacktrace:
adb logcat -s Nav:E
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
|
|
Dot-notation style names imply a parent by convention; AAPT was looking
for ShapeAppearance.Nav which doesn't exist. Parent is now
ShapeAppearance.Material3.Corner.None (all-zero base) with the top
corners overridden to 24dp.
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
|
|
Edit tool dropped the function header when inserting injectTestTrack/
buildTestTrack above it. Restore `fun addGpsPoint(...)` so the build compiles.
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
|
|
Long-press the record FAB while NOT recording to immediately save a
pre-built 30-minute west/return circuit from Honokohau Harbor. Uses the
existing startTrack/addPoint/stopTrack pipeline so the full save flow —
TrackSummarySheet, GPX file write, past-tracks list — all execute without
needing to move. Points carry manually-set timestampMs values (30 min in
the past, 2-min spacing) so the 2-minute duration validation passes.
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
|
|
Particle wind animation, Learn tab, log text/photo, departure picker,
outbound link markers, offline ColRegs + sailing reference content.
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
|
|
scatter
- MAX_AGE 8s → 25s: particles live much longer, drastically less twinkle
- activeN scales 60→300 with wind speed (0–25 kt): calm = sparse drift,
strong wind = dense streaks
- Zoom-out detection: scatter() fires immediately when latRange grows >1.8×,
instantly filling the new viewport instead of waiting for old particles to die
- Alpha uses sine curve (sin(π·age/MAX_AGE)) instead of linear fade:
particles ease in from birth, peak at mid-life, and ease out — no bright
birth flash that caused the twinkle effect
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
|
|
GPS settling produces many tiny position updates that each triggered a
1-second animateCamera(), forcing continuous symbol-collision
recalculation and label fade cycles.
Fix: skip animation entirely (moveCamera) when the new position is
< 3 m from the last one; use a shorter easeCamera(400 ms) for real
movement so the animation window is smaller.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Layout doesn't support landscape; rotating was causing a crash.
configChanges also prevents unnecessary activity recreation if the
system somehow delivers an orientation event anyway.
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
|
|
- Particles: vivid blue (30, 100, 255) instead of white — visible on
light charts background; thicker stroke 3.5px vs 2.5px
- Wind raster overlay: opacity 0.85 vs 0.60
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
When the viewport crosses ±180° longitude (e.g. Pacific Ocean view),
minOf/maxOf on raw longitudes produces lonWest/lonEast that are backwards.
Every particle then satisfies the out-of-bounds check and is respawned on
every frame → continue → never drawn.
Fix: compute lonSpan from screen-ordered corners (left edge / right edge)
with antimeridian wrap: lonSpan = lonEast >= lonWest ? span : span + 360.
Bounds check uses normalized particle longitude relative to lonWest mod 360.
Particle movement wraps longitude into [-180, 180] to stay consistent.
Also change particle color to white so it contrasts against the blue wind
raster overlay instead of blending into it.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
- MapLayerManager: default base preset changed from SATELLITE to CHARTS
- ParticleWindView: respawned particles now get a random age (0..MAX_AGE)
instead of always 0, preventing mass synchronized respawns after pan/zoom
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
MapLayerManager registers SOURCE_WIND = "wind-source" as a RasterSource
in the style builder. MapHandler was using the same ID for its GeoJsonSource,
causing MapLibre to throw on duplicate source IDs at style-load time.
Rename MapHandler's source to "wind-arrows-source".
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Same MapLibre 13.0.1 nullable corner fix — use listOfNotNull() and
guard on size == 4 before calling loadWindGrid.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
MapLibre 13.0.1 types VisibleRegion corners as LatLng? (nullable).
Use listOfNotNull() in both onDraw() and scatter() to guard against
null corners — mirrors the same fix already applied to MainActivity.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
LatLngBounds.latSouth/latNorth/lonWest/lonEast don't exist in
MapLibre 13.0.1. Derive bounds from VisibleRegion corner LatLng
points (.nearLeft/.nearRight/.farLeft/.farRight) which are stable
across SDK versions.
Fixes CI compilation failure in MainActivity and ParticleWindView.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
Document that default branch is always main, CI triggers on main,
and master must never be used. Prevents recurrence of the
master/main confusion that broke Firebase distribution.
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
|
|
|
|
Repo default branch is master; workflow was targeting main so CI
never ran and Firebase distribution never fired.
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
simulation
|
|
- 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
|
|
|
|
- WeatherApiService: batch endpoint getWeatherForecastBatch with
comma-separated lat/lon → List<WeatherResponse>
- WeatherRepository.fetchWindGrid: chunks 20 points into two calls of 10,
maps each response to a WindArrow at its returned lat/lon
- MainViewModel.loadWindGrid(bounds): builds a 4×5 grid from visible map
bounds, calls fetchWindGrid, emits to windGrid StateFlow
- MapHandler.setupWindGridLayer: GeoJSON source + SymbolLayer reusing the
wind-arrow icon; MapHandler.updateWindGridLayer: pushes feature collection
- MainActivity: stores mapLibreMapRef; calls setupWindGridLayer after style
loads; addOnCameraIdleListener fires loadWindGrid on every pan/zoom;
observes windGrid → updateWindGridLayer
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
|
|
|
|
- MapHandler.setupWindLayer: registers ic_wind_arrow bitmap, adds GeoJSON
source + SymbolLayer with rotation/size driven by direction/speed_kt props
- MapHandler.updateWindLayer(arrow): pushes updated feature to the source
- MainActivity: calls setupWindLayer after style load; fires loadWeather once
on first GPS fix (weatherLoaded flag); observes windArrow StateFlow to
drive updateWindLayer
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>
|
|
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>
|
|
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>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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>
|
|
GpxSerializer/GpxParser: full round-trip of all TrackPoint fields via
GPX 1.1 + nav: extensions namespace. 13 unit tests.
TrackStorage: MediaStore on API 29+ (no permission needed), direct file
I/O on API 24-28 (WRITE_EXTERNAL_STORAGE maxSdkVersion=28).
TrackRepository: stopTrack() is now suspend, writes GPX and returns
TrackSummary (distance nm, duration, max/avg SOG, avg wind, avg wave).
getPastTracks() lazy-loads from Documents/Nav/ on first call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
WaveView: animation speed = 8/period so long swell animates slowly;
amplitude ceiling raised to 42% of view height; whitecaps only when
windSpeedKt >= 12 (Beaufort 4).
InstrumentHandler.updateWaveState: sizes view height from swell height
(1ft→56dp, 8ft→160dp) and forwards windSpeedKt to WaveView.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Surface/background → #1C1B1F (WaveView sky), text tokens updated to
light M3 dark-mode values, status bar icons set to light.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|