summaryrefslogtreecommitdiff
path: root/android-app/app
AgeCommit message (Collapse)Author
2026-06-30fix: restore dark mode, fix depth tile source, show FADs by defaultPeter Stone
- Remove MODE_NIGHT_NO from NavApplication — system dark mode now respected; NightVision toggle (red-on-black) is separate and unaffected - Depth layer: swap empty OpenSeaMap depth tiles for NOAA CoastWatch ETOPO180 WMS which has full Pacific/Hawaii coverage - FADs default to visible (was off) since Hawaii DAR data is loaded at startup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): dashed bearing line on map for fishing targetPeter Stone
Adds setupFishingLayer() and updateFishingBearing() to MapHandler with destinationPoint() haversine helper. Draws dashed orange line from boat toward the computed fishing target bearing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): wire fishing mode FAB and overlay in MainActivityPeter Stone
Adds FishingOverlayView + fab_fishing to layout, initializes FishingModeManager, and wires FAB click to toggle fishing mode with SST layer and target overlay. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): FishingOverlayView card UI and ic_fish vector drawablePeter Stone
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): FishingModeManager with target zone computationPeter Stone
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): SST raster layer from NOAA CoastWatchPeter Stone
Adds SST source/layer to MapLayerManager, SST toggle to LayerPickerSheet UI and wires it up in MainActivity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat(fishing): FishingTarget + RigAdvisor logicPeter Stone
Also fix pre-existing test compile errors in WeatherApiServiceTest (nullable list access) and add missing metresToFeet/formatFt helpers to InstrumentHandler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat: UX improvements + HAW FAD data + fishing mode planPeter Stone
Dark mode: - Force light mode (AppCompatDelegate.MODE_NIGHT_NO) in NavApplication Log screen: - Tracks sorted newest-first (sortedByDescending startMs) - Photos/entries saved during a track tagged with trackId; ship's log shows only standalone entries (trackId == null) - "Plan Trip" button moved from safety screen to log screen header row - Pre-trip report auto-generates (newAutoInstance) when a track starts Safety screen: - Removed "Plan Trip" button (now in log screen) - Quit button moved to top title row - Anchor Watch section collapses by default (tap header to expand) - Hardware Data Sources section collapses by default (tap header to expand) Notifications + idle: - Tracking notification becomes persistent (setOngoing true) while recording - Notification title changes to "Recording track" when active - App exits automatically after 3 hours of idle (resetIdleTimer via onUserInteraction + onResume; suspended while recording) FAD layer: - FadData.kt: 18 Hawaii Island DLNR FAD coordinates hardcoded (source: DLNR Division of Aquatic Resources 6/14/07) - FAD GeoJSON populated on style load from static data Fishing mode plan: - docs/superpowers/plans/2026-06-30-fishing-mode.md: 7-task plan for RigAdvisor, SST tiles, FishingModeManager, FishingOverlayView, steering bearing line, and FAB integration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30feat: add ocean layers — depth contours, seamarks toggle, FADsPeter Stone
- MapLayerManager: depth contour tiles (OpenSeaMap), seamarks made toggleable (was always visible), FAD pref; layer order is now satellite → charts → wind → depth → seamarks - MapHandler: FAD GeoJSON layer (orange circles + zoom-10 labels), setupFadLayer / setFadLayerVisible / updateFadLayer helpers - LayerPickerSheet: wire seamarks, depth, FAD switches; fix chip binding direction bug (DepthUnit.METERS side was swapped) - layout_layer_picker_sheet: add OCEAN LAYERS section with three toggles (seamarks/nav aids, depth contours, FADs) - MainActivity: call setupFadLayer on style load; wire all three ocean layer callbacks in buildLayerPickerSheet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05Fix wind grid deserialization: make forecast-only fields nullableClaude
The batch wind-grid endpoint only requests windspeed_10m and winddirection_10m, so temperature_2m, precipitation_probability, and weathercode are absent from its response. Moshi required these fields and threw, causing fetchWindGrid to silently fail. Made those three fields nullable with null defaults in WeatherHourly. Updated the two call sites in WeatherRepository that access them via the full forecast endpoint to use safe-call syntax. https://claude.ai/code/session_017TwkSjhzhmTcHKmVG5MWUa
2026-06-04Fix location-retention regression: stop GPS when backgrounded and idleClaude
The previous fix (48ae3b0) dropped to ECONOMY (5 s interval) on app pause, but never stopped location requests. The fusedLocationClient continued polling indefinitely in the background even with nothing recording or anchor-watching — accessing location without any need. Fix: - Add PowerMode.NONE to represent "no updates in flight" - Add ACTION_STOP_UPDATES to LocationService: calls stopLocationUpdatesInternal() and parks _currentPowerMode at NONE - Guard startLocationUpdatesInternal() against PowerMode.NONE so a stray setPowerMode(NONE) call can never request zero-interval updates - Change _currentPowerMode initial value from FULL to NONE (accurate: no updates are running at process start) - throttleLocationIfIdle() now sends ACTION_STOP_UPDATES instead of ACTION_START_ECONOMY; the existing restoreLocationMode() on onResume already sends ACTION_START_ECONOMY which transitions NONE→ECONOMY correctly since NONE != ECONOMY bypasses the no-op guard Tracking and anchor-watch paths are unchanged: throttleLocationIfIdle() skips the stop if either is active, and onResume sends ACTION_START_FULL when recording is still in progress. https://claude.ai/code/session_0126zvB69ccVoFtx2o96oaE2
2026-06-04Fix wind particles never appearingClaude
Two bugs prevented wind particles from rendering: 1. Retrofit URL-encoded commas in the batch lat/lon query strings (%2C instead of ,), so Open-Meteo rejected every grid fetch silently. Fixed with encoded=true on the @Query annotations. 2. loadWindGrid() was only called from onCameraIdleListener, so particles never started unless the user manually panned. Added an eager trigger on the first GPS fix using the map's current visible region. Also added a log warning when fetchWindGrid fails so future failures are visible in logcat. https://claude.ai/code/session_017TwkSjhzhmTcHKmVG5MWUa
2026-06-04Spatial wind particles: grid interpolation, fast projection, density/opacityClaude
- Particles now use bilinear interpolation of the 4×5 wind grid so speed and direction vary by position, swirling around weather systems. Particles are hidden until the first grid arrives (no stale fallback). - Replace 150–300 per-particle toScreenLocation() JNI calls with a single 3-point affine transform computed once per frame. Rendering stays smooth during map panning. - Per-particle tail direction is now derived from the local (u,v) wind vector rotated into screen space by the map bearing. - Density: N 300→600, activeN range [150,600] (was [60,300]). - Opacity: max alpha 200→110, min 15→8; stroke 3.5→2.5 px. Wire setWindGrid() in MainActivity windGrid.collect alongside the existing updateWindGridLayer() call. https://claude.ai/code/session_017TwkSjhzhmTcHKmVG5MWUa
2026-06-04Fix wind particles not visibly movingClaude
Two bugs caused particles to appear stationary: 1. Speed constant was 0.03 (particles moved ~7 px/s at 10 kt — barely perceptible). Changed to 0.1 so a 10-kt particle crosses the full viewport in MAX_AGE seconds (~24 px/s), producing clear streaming. 2. Individual respawn assigned a random age in [0, MAX_AGE). Half of all newly spawned particles were born old and faded out almost immediately without traversing visible distance, giving a "static noise" look rather than flowing motion. Respawn now starts near age 0 (with a 0–2 s jitter to desynchronise simultaneous respawn bursts). https://claude.ai/code/session_017TwkSjhzhmTcHKmVG5MWUa
2026-06-03PMIM-1 hardware support: UDP, IIXDR, inclinometerClaude
Transport: - NmeaStreamManager supports TCP and UDP modes - NmeaConnectionPrefs replaces hardcoded 192.168.1.1:10110; defaults to UDP/10110 matching PMIM-1 spec (ESP32 AP at 192.168.4.1) - LocationService reads prefs via startNmeaFromPrefs() Parsing: - NmeaParser.parseXdr() handles $IIXDR attitude (HEEL/PTCH in degrees) and baro (pressure in bar → hPa, air temp °C) - New AttitudeData and NmeaBaroData sensor types UI: - Heel + pitch row in instrument sheet (hidden until data arrives) - InclinometerView: canvas circle with rotating waterline (heel) and displaced dot (pitch), colorPrimary-tinted, respects night mode - Inclinometer overlay top-left of map, appears on first attitude packet - NMEA baro overrides device sensor reading when available https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-06-03Add night vision mode toggleClaude
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
2026-05-30Trip report: map, photos, structured layout, preferred unitsClaude
Replaces monospace text block with: - Full-bleed MapLibre track map (260dp) at the top - Structured stats row (distance / avg speed / max speed) - Conditions row (wave height, temp) in user-preferred units - Log entries with full-width photo thumbnails - Pirate mode Easter egg preserved via long-press title - Speed, temp, and depth formatted with UnitPrefs https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-30Redesign trip report: structured layout, remove generate button, fix photo ↵Claude
entries Layout: - Stats displayed as three large-number columns (distance / avg kt / max kt) - Date and time range as a clean header above stats - Conditions row (seas + temp) conditionally shown - Log entries rendered as a proper list with HH:mm timestamps - No GENERATE REPORT button — report auto-generates on open; RETRY shown only on error - Pirate mode (long-press title) still renders monospace narrative as before Generator: - Time format changed from 4-digit HHmm to HH:mm - "(photo only)" entries display as 📷 instead of literal "(photo only) [photo]" https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-30Remove Generate Trip Report button from Log tabClaude
Each track row already has a REPORT button; the standalone button was redundant. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-30Fix VoiceLogFragment: pass onReport to SavedTrackAdapter (CI compile error)Claude
SavedTrackAdapter gained an onReport callback when the REPORT button was added to the track list. VoiceLogFragment was still using the old single-lambda form. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-28Tech debt: Room list fast-path, delete AnchorWatchHandler orphan, ↵Claude
LogbookRepository thread safety Room fast-path (item 3): - TrackEntity.toShallowSavedTrack() reconstructs SavedTrack from cached summary data using two placeholder TrackPoints that preserve correct endMs for logbook filtering without parsing GPX - TrackRepository.getSavedTracksFromRoom() returns shallow tracks sorted by startMs - MainViewModel.init{} now shows Room data immediately (no spinner on warm start), then replaces with full GPX-parsed tracks once background load completes AnchorWatchHandler orphan (item 4): - Deleted ui/anchorwatch/AnchorWatchHandler.kt — the anchor depth/rode form was inlined into SafetyFragment; this Fragment had no navigation entry points left - Deleted layout/fragment_anchor_watch.xml (only referenced by the deleted Fragment) LogbookRepository thread safety (item 6): - Added @Synchronized to save(), getAll(), and reload() to prevent concurrent mutation of the entries list and nextId counter across coroutine contexts https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-28Merge claude/whats-next-0XCO3: tech debt items 1, 2, 5Claude
- Deleted com.example.androidapp ghost package (18 stale source files) - Fixed tide model package declarations and HarmonicTideCalculator imports - Symlinked test-runner TackDetector to android-app canonical source - Fixed forecast to use current wall-clock hour slot instead of app-start hour https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-28Tech debt: delete com.example.androidapp ghost package, symlink ↵Claude
TackDetector, fix forecast current hour - Deleted all 18 stale source files in com.example.androidapp (main + test) that duplicated org.terst.nav implementations from an earlier namespace - Fixed the 3 tide model files (TidePrediction/Station/Constituent) whose package declarations read com.example.androidapp.data.model despite living in org.terst.nav.data.model directories - Fixed HarmonicTideCalculator.kt imports to match the corrected package - Fixed TideModelTest.kt package declaration - Migrated HarmonicTideCalculatorTest to org.terst.nav.tide with correct imports - Replaced test-runner's TackDetector.kt copy with a symlink to the android-app canonical source so the two can never diverge again - Fixed addGpsPoint to pick the current wall-clock hour's forecast slot instead of always using the app-start hour (first item in forecast list) https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-28Wire forecast wind direction into recorded TrackPointsClaude
Uses marineConditions.windDirDeg (refreshed every 20 min / 10 nm) falling back to forecast.windDirDeg (initial Open-Meteo fetch) to compute a boat-relative true wind angle: twa = (windDirDeg - cogDeg + 360) % 360. Stores windSpeedKnots, windAngleDeg, and isTrueWind=true on every GPS fix that has forecast data, enabling proper Tack/Jibe classification in buildLogEvents() instead of the "Maneuver" fallback. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Sort track history chronologically; add Report button to track listClaude
- getSavedTracks() now returns tracks sorted by startMs ascending so the overview list shows oldest→newest - item_saved_track.xml gets a small REPORT text button below the stats row - SavedTrackAdapter wires onReport callback; SavedTracksFragment navigates to TripReportFragment.newInstanceForSavedTrack() after selecting the track - TripReportViewModel.generateReportForSavedTrack(track) generates a post-trip narrative from a completed SavedTrack's points + logbook entries - TripReportFragment detects ARG_FOR_SAVED_TRACK, reads selectedTrack from MainViewModel, and uses the saved-track variant; Refresh button re-runs the same variant https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Fix logbook entries not reloading after SAF re-auth post-reinstallClaude
LogbookRepository loaded entries once at init; after reinstall the SAF tree URI pref is wiped, so it loaded empty from the cleared legacy path and held that empty list for the session — even after the user re-granted SAF access. Fix: add LogbookRepository.reload() and call it in MainViewModel.onSafDirectorySelected() alongside reloadPastTracks(). The logbook JSON and photos already live in the SAF tree and survive reinstall; this just ensures they're read at the right moment. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Add Room database as track summary cacheClaude
Adds TrackEntity/TrackDao/NavDatabase (Room v2.6.1). TrackRepository writes to Room on stopTrack() and importTrack(), and backfills existing tracks on first cold-start when the Room cache is empty. GPX/SAF remains source-of-truth for reinstall; Room is a fast metadata cache for future list-view optimisation. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Embed anchor watch form inline in Safety DashboardClaude
Replaces the "Configure Anchor Watch" button with depth and rode EditTexts directly in the anchor card; radius updates live via TextWatcher. Removes the separate AnchorWatchHandler overlay flow and onConfigureAnchor() from SafetyListener. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Add unit tests for PreTripReportGeneratorClaude
Covers estimatedSogKt (hull-speed cap, point-of-sail ordering, light-air, hull-length scaling, overpowered vs sweet-spot) and generateReport (condition window size, Now label, heading range, outbound nm, sail plan logic, watch items, similar trips). https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Add RACE mode to TackDetector for rapid mark-rounding detectionClaude
Introduces TackMode enum (CRUISING/RACE) and per-mode Config, replacing the previous single set of hardcoded constants. RACE config: T_SETTLE=12s, T_MANEUVER=20s, STAB_MAX=30°, MIN_GAP=20s, START_SKIP=20s — tighter windows for quick successive tacks at race starts and mark roundings. CRUISING remains the default. Two new tests demonstrate the behavioral difference: RACE fires earlier (past 20s cold-start vs 60s) and keeps separate events for a 50s gap that CRUISING deduplicates (50s < 60s MIN_GAP). 18/18 passing. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-27Add pirate mode Easter egg to trip reportClaude
Long-press the "Trip Report" header flips the narrative into pirate dialect (☠ VOYAGE header, "Made X nm by the log", "Avast!" log-entry prefixes, closing "Fair winds and following seas. Arr."). Long-press again returns to standard nav voice. Seamanlike output remains the default; pirate mode is purely opt-in. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-26Persist logbook JSON and photos in SAF so they survive reinstallClaude
- LogbookStorage: reads/writes nav_logbook.json and photos into the SAF logbook/ subfolder, using the same saf_tree_uri pref as TrackStorage. Automatically migrates legacy getExternalFilesDir JSON on first SAF write. Falls back to external files when SAF is not yet configured. - LogbookRepository: expose cameraDir (cache-backed temp for FileProvider), copyPhoto(Uri), and importPhoto(File) instead of the old photoDir. - VoiceLogFragment / TrackDetailSheet: camera writes temp to cacheDir then imports to SAF after capture; gallery delegates to copyPhoto(); photo display and decodeThumbnail handle both content:// URIs and file paths. - file_paths.xml: add cache-path entry so FileProvider can serve camera temp files from cacheDir/nav_camera/. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-26Fix gallery photo timestamp and map pin not updating after note saveClaude
Gallery photos were always anchored to track.endMs with the final GPS position. Now reads EXIF DateTimeOriginal (parsed in device local time) after copying the file; if the timestamp falls within the track window the note is positioned at the correct time and nearest track point. Notes taken outside the window fall back to the previous behaviour. Map note layer was never refreshed after saving — drawNotes() ran once at style-load time and the GeoJsonSource was never updated. Added refreshNoteLayer() which updates the existing source or creates it if no notes existed before. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-26Fix ANR, false tack detections, and empty-state flash on track loadClaude
ANR: getSavedTracks() had no IO dispatcher — TackDetector ran on main thread at O(n²) cost (~77M ops for a 2.5h track). Added withContext(IO). TackDetector: replaced O(n) list.filter with O(log n) binary-search subList for before/after windows; O(n²) → O(n log n). Raised MIN_DELTA 60°→75° to stop detecting ordinary course corrections as maneuvers. Removed MAX_DELTA cap entirely — 180° crash-tacks are valid; the circularMAD stability windows are the real quality gate. TrackDetailSheet: dropped the arbitrary abs(delta)>70 Tack/Jibe heuristic. Now uses true wind angle when available for classification; falls back to "Maneuver" when TWD is absent. SavedTracksFragment: added isLoadingTracks StateFlow (MainViewModel); combine() suppresses the empty-state layout while the initial IO load runs so it no longer flashes "No saved tracks yet" before data arrives. 16/16 TackDetector tests pass. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
2026-05-26Redesign tack detection: time-based windows + circularMAD stabilityClaude
Replace the point-count window (HALF_WIN=2) with 30s time-based settle windows that give consistent reliability regardless of GPS update rate (1 Hz FULL vs 0.2 Hz ECONOMY). Key changes: - Before/after windows are time-based (T_SETTLE=30s) around a guard zone (T_MANEUVER=30s), not point counts. - Stability uses circularMAD (<20°) across all fixes in each window, replacing the single-pair spread check. This correctly handles 0°/360° wrap and suppresses random anchor-watch COG noise (MAD≈90°). - No SOG gate: averaging 30 fixes over 30s reduces noise by √30 regardless of boat speed. A boat at anchor has MAD≈90° and is rejected by the stability check. - MAX_DELTA raised to 160° (covers deep jibes the old 140° missed). - START_SKIP reduced to 60s (stability check handles cold-start noise). - De-duplication uses adjacent-candidate comparison so a long stream of raw candidates from one maneuver stays in a single group even if it spans >MIN_GAP_MS in total. - Position refined to max instantaneous heading rate within the maneuver zone. - Mode: CRUISING (conservative). Race mode noted as future backlog. 16/16 unit tests pass at 1 Hz (FULL mode GPS rate). https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-26Add notes to finished tracks, single Nav voice, fix speed coloringClaude
- TrackDetailSheet: + Add note button opens a dialog with text, camera, and gallery. Entry is saved with timestampMs=track.endMs so it lands inside the track's time window and appears automatically in the event log. logEventAdapter.update() refreshes the list after save. - TripReportGenerator: removed NarrativeStyle enum and 4-style branch. generateNarrative() now emits a concise PASSAGE header (date, time range, duration), stats line (nm / avg kt / max kt), optional conditions (seas, temp), and a timestamped deck log. Fragment and layout updated to remove the ChipGroup style picker. - TrackColors: replaced Expression.get("color") — which silently fails for lineColor on LineLayer in MapLibre Android 11.x — with Expression.step(Expression.get("speed"), ...) that maps the numeric "speed" property directly to color literals. "color" string property removed from speedSegments() features. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-26Fix camera to open system camera app; harden logbook storage; add backlogClaude
Camera: - Replace ActivityResultContracts.TakePicture() with explicit StartActivityForResult + Intent(ACTION_IMAGE_CAPTURE) + EXTRA_OUTPUT so the full system camera app always opens (not a sub-view preview) - FLAG_GRANT_WRITE_URI_PERMISSION added for camera app FileProvider access LogbookStorage: - getExternalFilesDir() can return null when external storage is unavailable; fall back to filesDir for both storageFile and photoDir so saves never silently fail on devices without external storage Backlog (worklog.md): - Add notes/photos to finished tracks + include in trip report - Trip report voice: replace generic style picker with single Nav voice - Fix tack detection false positives at low SOG - Investigate/fix speed coloring in MapLibre Android 11.x https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-26Fix LocationService current TODO; show log entries in track detail viewClaude
LocationService: - Add _currentSpeedKt/_currentDirectionDeg to companion state - Add setCurrentConditions() companion function - snapshot() now returns real current data instead of null - MainActivity.applyConditions() calls setCurrentConditions() on each marine conditions update TrackDetailSheet: - buildLogEvents() filters NavApplication.logbookRepository entries by track time window (startMs..endMs); entries without GPS snap to nearest track point by timestamp - Note/photo events added to the event log list with 📝/📷 icons - drawNotes() adds a green CircleLayer for note/photo markers on the map - LogEvent extended with photoPath field - LogEventAdapter shows a downsampled photo thumbnail when present item_log_entry.xml: add iv_log_thumb ImageView (hidden by default) https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-25Fix bugs from static analysis; add test coverage for new codeClaude
Bugs fixed: - LogbookStorage.copyFromUri: return null when ContentResolver returns null stream instead of returning a path to an unwritten file - VoiceLogFragment adapter: decode photos as downsampled thumbnails (128x96 target) using BitmapFactory inSampleSize instead of loading full-resolution bitmaps on the main thread Tests added: - LogbookRepositoryTest: ID sequencing, insertion order, storage delegation, reload from persisted state, lat/lon preservation - TripReportGeneratorTest: log entry time-range filtering, distance calculation, empty-points edge case, all narrative styles smoke-tested https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-25Merge claude/optimize-nav-location-tracking-Vac5Z into mainClaude
Resolve conflict in TackDetector.kt: keep null-safe lastTackMs check from main (branch had a NPE-unsafe subtraction on nullable Long). https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-25Persist log entries and photos; stamp with GPS locationClaude
- LogbookStorage: Moshi-backed JSON persistence to getExternalFilesDir, photos saved to Pictures/Nav/ instead of cache - LogbookRepository: file-backed replacement for in-memory store, loads saved entries on init so they survive app restarts - VoiceLogViewModel: expose entries StateFlow, accept lat/lon in save() - VoiceLogFragment: switch camera to TakePicture(Uri) via FileProvider for full-res photos; gallery copies content URI to persistent storage; passes current GPS position to save(); shows scrollable entries list - MainViewModel: expose currentPosition StateFlow updated on each GPS fix - AndroidManifest: add FileProvider for Pictures/Nav/ directory - TripReportViewModel: updated to use LogbookRepository https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-21Revert detail sheet drawTrack to original single-Feature static colorClaude
The speed-color expression approaches all failed silently. Reverting to the exact pre-speed-color form (single Feature LineString, static #2B8FC4) to re-establish a visible track line while diagnosing why the FeatureCollection/expression approach doesn't render. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-21Fix speed-colored track: pre-compute hex color per segment, use get("color")Claude
Instead of asking MapLibre to interpolate/step colors at render time (which was silently failing), pre-compute the CSS hex color for each segment in speedSegments() and store it as a "color" property. speedColorExpression() is now Expression.get("color") — a simple property lookup that MapLibre parses as a CSS color for line-color. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix savedTracks silently empty when getSavedTracks() throwsClaude
getSavedTracks() was called inside .onSuccess{} with no error boundary; any exception from toSavedTrack() crashed the coroutine silently after _pastTracks was already set, leaving _savedTracks empty (tracks visible on the main map dim layer but missing from the Log tab list). Wrap every getSavedTracks() call in runCatching, and make toSavedTrack() inside getSavedTracks() skip individual bad tracks via mapNotNull so one corrupt GPX can't blank the whole list. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix invisible speed-colored track: use step() with hex string literalsClaude
Expression.color(Color.parseColor()) inside interpolate() stops was silently failing to render on device. Replace with Expression.step() and Expression.literal(hexString) which MapLibre's renderer handles natively for line-color. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix missing imports for speedColorExpression and speedSegments in MapHandlerClaude
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Color track lines by boat speed using MapLibre data-driven stylingClaude
Each focused track is rendered as per-segment LineStrings tagged with average SOG, interpolated blue→cyan→green→amber→red for 0→13+ knots. Applies to both the main map focus layer and the track detail sheet. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix Long overflow in TackDetector: var lastTackMs = Long.MIN_VALUE caused ↵Claude
subtraction overflow, filtering every tack
2026-05-20Fix build: TrackDetailSheet is Fragment not DialogFragment, use transaction ↵Claude
from VoiceLogFragment
2026-05-20Fix build: TrackDetailSheet is Fragment not DialogFragment, use transaction ↵Claude
from VoiceLogFragment