| Age | Commit message (Collapse) | Author |
|
- 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>
|
|
MainActivity
InstrumentHandler: direction arrows (SKY/OCEAN palettes), WaveView state,
metres→feet conversion, bearing formatting, all helpers top-level for TDD.
MainActivity: setupHandlers wires all new view refs; observeDataSources
passes cogBearingDeg, twsBearingDeg, raw metres to handler; depth collector
wired from nmeaDepthDataFlow.
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>
|
|
DirectionArrowView: rotating notched-chevron compass indicator in
SKY (grey) and OCEAN (blue) palettes, with bearing normalization.
WaveView: animated swell + wind-chop canvas divider — sky/sea
gradient fills, shimmer line, whitecap highlights; self-animates
via postInvalidateOnAnimation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
forecast styles
|
|
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>
|
|
- Switch default map view to Satellite
- Add Windy (partial alpha) and OpenSeaMap overlays
- Add custom user position icon (ship arrow) with heading rotation
- Update TrackPoint to support rich instrument/weather metadata
- Change track visualization to a dotted red line
- Robustify NavApplication.isTesting with Espresso detection
Co-Authored-By: Gemini CLI <gemini-cli@google.com>
|
|
- Use BeforeClass to set isTesting in NavApplication
- This ensures MapLibre is bypassed correctly even when ActivityScenarioRule starts before @Before.
Co-Authored-By: Gemini CLI <gemini-cli@google.com>
|
|
initialization and guarding MapLibre
- Ensure lateinit UI properties are assigned before setupMap() and setupHandlers()
- Skip MapLibre initialization and lifecycle calls when NavApplication.isTesting is true to avoid emulator Vulkan crashes
- Guard MapView lifecycle calls in onResume, onStart, etc.
Co-Authored-By: Gemini CLI <gemini-cli@google.com>
|
|
6 stories covering findings from codebase audit:
- ct-remove-mock-tidal: remove random fake tidal currents near Solent
- ct-wire-tidal-to-map: wire tidalCurrentState to MapHandler (never called)
- ct-wire-anchor-to-map: wire anchorWatchState to map overlay (text-only)
- ct-show-wind-direction: display TWD fetched but not shown in sheet
- ct-show-swell-direction: display swell dir fetched but not shown
- ct-fix-weather-fallback: remove silent SF fallback in WeatherActivity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.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>
|
|
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>
|