| Age | Commit message (Collapse) | Author |
|
All past tracks render as solid dark navy (#0B3050) at 45% opacity,
2.5px — no dashes. A second focus layer (#2B8FC4, full opacity, 4px)
paints on top when a track is selected, making it pop without changing
the dim layer's data. Active recording stays red.
The dim layer always contains every past track; the focus layer gets
the selected track's points independently via setFocusedTrackPoints().
Cleared on dismiss so all tracks return to equal dim opacity.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
sheet
Recenter button was reappearing over overlays because the isFollowing
flow kept calling fadeIn(fabRecenter) even when an overlay was visible.
Added overlayShowing flag — the flow skips FAB updates while an overlay
is shown. hideOverlays() now correctly restores fabRecenter visibility
based on the actual following state instead of just resetting alpha.
Speed up/Slow down events fired on every 1.5 kt GPS jitter, flooding
the log with noise. Removed the detection loop entirely — no reliable
motor on/off signal is available from GPS alone.
TrackDetailSheet converted from a full-screen Fragment to a
BottomSheetDialogFragment: opens at half-screen height so the track
route is visible on the map behind it, draggable to full height, 20%
background dim. SavedTracksFragment now calls hideOverlays() then
sheet.show() instead of fragment-replacing the overlay container.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Removed the spurious constraintBottom_toBottomOf="parent" from forecast_row:
ConstraintLayout was vertically centering it between the wave and the sheet
bottom (default bias 0.5) instead of placing it flush below the wave.
Also aligned light-mode wave_sea_bottom (#074B68 → #0D2137) with the
forecast_row background so the wave tail blends seamlessly in light theme.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
- hideOverlays() now resets bottomSheetBehavior to COLLAPSED/not-hideable,
so the conditions sheet reappears correctly when returning from any overlay
(previously it stayed hidden after navigating away via the track browser)
- Remove btn_saved_tracks from the conditions sheet (layout + click handler)
- Add btn_saved_tracks to the Log screen layout and wire it in VoiceLogFragment
via MainActivity.showSavedTracks(); tracks live with the log, not with weather
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
- SavedTracksFragment was missing adapter.submitList(tracks) after the
collect block rewrite — tracks loaded fine (16 found via SAF) but the
RecyclerView adapter never received them, leaving the list visually blank
- showOverlay() now also hides fabRecenter; it was not in the hide list so
the map's Recenter button remained visible on top of the fragment overlay
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Intent filter fixes:
- Remove pathSuffix=".gpx" + scheme restriction from the application/octet-stream
ACTION_VIEW filter; pathSuffix matches the URI path, not the filename, so it
never fired against file manager content URIs (opaque paths like /provider/uuid).
The existing display-name check in handleIncomingGpx() is the correct gate.
- Add ACTION_SEND filter for application/octet-stream; Nav was invisible in share
sheets because most file managers send this type for .gpx files.
SAF-based track storage (API 29+):
- Replace fragile SharedPrefs URI cache + OWNER_PACKAGE_NAME MediaStore query with
Storage Access Framework as the primary write/read path. The user grants access to
their tracks folder once via ACTION_OPEN_DOCUMENT_TREE; DocumentFile.listFiles()
finds all GPX files without relying on MediaStore ownership (which Android clears
on uninstall). After reinstall, a one-tap "Restore track access" button in Saved
Tracks re-authorizes the same folder and all tracks immediately reappear.
- MediaStore path (existing) is kept as fallback for tracks saved before this change
- Add SafState enum (CONFIGURED / PERMISSION_LOST / NOT_CONFIGURED) in TrackStorage
- TrackRepository exposes safState() and initSafDirectory() thin wrappers
- MainViewModel exposes safState StateFlow and onSafDirectorySelected()
- SavedTracksFragment observes safState and shows contextual setup / re-auth button
- MainActivity registers safPickerLauncher and exposes launchSafPicker()
- Add androidx.documentfile:documentfile:1.0.1 dependency
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
- Enable buildConfig generation explicitly (AGP 8+ disabled it by default);
fixes Unresolved reference 'BuildConfig' in MainActivity
- Add required onCancellation lambda to cont.resume() in TrackStorage;
fixes 'No value passed for parameter onCancellation' with coroutines 1.10.2
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
- Start LocationService at ECONOMY (5 s, PRIORITY_BALANCED_POWER_ACCURACY)
instead of FULL (1 Hz, PRIORITY_HIGH_ACCURACY); promote to FULL only
while a track is actively recording and demote back when recording stops
- Use PRIORITY_BALANCED_POWER_ACCURACY for ECONOMY and ANCHOR_WATCH modes
so the GPS chip idles and network/passive sources handle light-duty fixes
- Add ACTION_START_ECONOMY / ACTION_START_FULL intents so MainActivity can
drive mode transitions from lifecycle callbacks
- onPause: drop to ECONOMY if not recording or anchor-watching; onResume:
restore the appropriate mode so the first visible frame has a fresh rate
- Stop-anchor-watch now returns to ECONOMY instead of FULL (anchor drop
rarely coincides with active track recording)
- Remove ACCESS_BACKGROUND_LOCATION from manifest — the foreground service
with foregroundServiceType="location" already covers background access
- Add 5 m minimum-distance filter to DeviceGpsProvider so stationary devices
don't generate spurious location callbacks
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Android clears OWNER_PACKAGE_NAME for all MediaStore entries when an app is
uninstalled. Files survive on disk in Documents/Nav/ but the ownership query
returns 0 rows after reinstall. Fix: if the first load finds nothing, call
MediaScannerConnection.scanFile() on Documents/Nav/. Because our app triggers
the scan, the MediaScanner re-registers each file with our package as owner,
restoring the query results on the immediate retry.
Also log BuildConfig.VERSION_NAME + VERSION_CODE in the startup message so
the build is identifiable directly from the in-app debug log.
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
Google Files uses application/octet-stream for .gpx because Android 16's
MimeTypeMap has no entry for that extension. The ExternalStorageProvider
content URI encodes the full file path, so pathSuffix=".gpx" keeps the
ACTION_VIEW filter targeted without matching all binary files.
ACTION_SEND (share sheet) filters added for typed MIME variants; code now
handles both actions — EXTRA_STREAM for SEND, intent.data for VIEW.
For octet-stream and generic XML types, OpenableColumns.DISPLAY_NAME is
checked before parsing (uri.lastPathSegment is an opaque ID for content
URIs, not the filename). All decisions logged to NavLogger.
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
Android's MimeTypeMap does not include application/gpx+xml for .gpx on
all devices, so file managers fall back to text/xml or application/xml.
Add intent-filters for both XML MIME types and check OpenableColumns
DISPLAY_NAME in code before parsing — so Nav appears in the chooser for
XML files but only processes ones named *.gpx.
Remove the broken file:// pathSuffix filter (irrelevant on Android 16,
also syntactically invalid when combined with mimeType in a single <data>
element). Remove unnecessary BROWSABLE category from file-open filters.
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
- AndroidManifest: add application/gpx and file:// intent-filters so Nav
appears in the chooser on devices that don't know application/gpx+xml
- MainActivity: check file extension as fallback when MIME type is generic
- TrackStorage: filter MediaStore fallback query by OWNER_PACKAGE_NAME so
the system grants access to our own files without READ_EXTERNAL_STORAGE,
which also survives app reinstall (same package name = same ownership)
- TrackRepository: add reloadTracks() to force a fresh load from storage
- MainViewModel: add reloadPastTracks() called after location permission
grant so the first-launch race condition can self-heal
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
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
|
|
Saved tracks overlay was immediately hidden after showing because
bottomNav.selectedItemId = nav_map fires the nav item listener, which
calls hideOverlays(). Removed that line — the map tab is already
selected when the instrument sheet is open.
GPX file handler:
- Manifest: intent-filter for application/gpx+xml + singleTop launch
mode so onNewIntent is called when app is already running
- MainActivity: handleIncomingGpx() called from onCreate + onNewIntent;
parses the URI on IO dispatcher and hands points to ViewModel
- MainViewModel: addImportedTrack() prepends parsed points to pastTracks
and savedTracks state flows (displayed in session only, not persisted)
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
|
|
MediaStore.Files.getContentUri("external") queries require READ_EXTERNAL_STORAGE
on Android ≤ 12 and have no clean permission path on Android 13+ for non-media
files. openInputStream() on a content URI the app owns works on all versions
without any permission — so the fix is to persist each URI at save time.
On save: call persistUri(uri) after the IS_PENDING=0 update, storing the
content URI in SharedPreferences (nav_track_uris).
On load: iterate stored URIs first and open each with openInputStream() — no
MediaStore query, no permissions needed. The existing MediaStore query is kept
as a fallback for tracks saved before this change; any track found there is
auto-migrated into SharedPreferences so future launches skip the query too.
Invalid URIs (file deleted) are cleaned up from SharedPreferences automatically.
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
Co-authored-by: Claude <noreply@anthropic.com>
|
|
RELATIVE_PATH equality matching was returning 0 rows — MediaStore
normalises the path differently across Android versions and devices.
DISPLAY_NAME LIKE nav_%.gpx is unique enough on its own.
Also log path + IS_PENDING per found file to aid future diagnosis.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- 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>
|
|
- Extract GeoThrottle inner class in MainViewModel; eliminates 6 state
vars and duplicate throttle logic in loadConditions/loadWindGrid
- Fix android.util.Log.e and kotlin.math.* fully-qualified refs now that
imports are present
- UnitPrefs: wrap valueOf() in runCatching to handle corrupt pref values
- VesselRepository.init: forEach { add() } → mapTo() for both lists
- TrackRepository: remove noisy cached-log else-branch that fired on
every stopTrack() call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
UnitPrefs: getString() returns String? even with a non-null default,
so !! crashes on corrupted prefs. Replaced with explicit elvis fallback
to the same enum default.
MainViewModel: forecastResult.getOrThrow() is logically safe (when-branch
guards it) but unnecessarily brittle. Replaced with getOrNull() ?: emptyList().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
MediaStore.query() throws SecurityException if storage permission hasn't
been granted yet. The ViewModel init{} fires before the permission dialog
result, so the coroutine crashed the app on first launch.
- Wrap contentResolver.query() in runCatching in loadViaMediaStore()
- Wrap getPastTracks() call in init{} with runCatching; log failure,
don't crash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Root cause: getPastTracks() was only called inside stopTrack(), so
saved tracks were never loaded unless a new track was recorded in
the same session. Added init{} block to MainViewModel to load them
at startup.
Logging added:
- getPastTracks(): first-call vs cached, count + point sizes
- loadViaMediaStore(): cursor row count, per-file parse attempt,
point count per file, any openInputStream null or parse errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
versionCode = github.run_number (increments each CI run)
versionName = "1.0.<run> (<short-sha> <commit subject>)"
e.g. "1.0.47 (2123351 fix: keep bottom nav visible when panning)"
Local builds show "1.0-local (1)" as fallback.
Firebase release notes also show the version string.
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>
|
|
Previous gate (5 min AND 2 nm) meant any pan > 2 nm triggered a
fresh fetch regardless of how recently one completed. Raised to
20 min OR 10 nm — marine conditions don't change meaningfully
at finer resolution than that.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
showOverlay/hideOverlay already managed fabRecordTrack visibility;
extend the same pattern to fabLayers so it disappears on safety,
log, vessel, learn, and anchor watch screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Camera idle: debounce 1.5s so rapid panning/zooming collapses into one fetch
loadWindGrid: skip if center moved < 10 nm AND last fetch < 15 min ago
(was unbounded — fired on every camera-idle with no gate at all)
loadConditions already had a 5 min + 2 nm gate; unchanged
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
|
|
Resolves LatLngBounds conflict in ParticleWindView.scatter() — keeps
the correct MapLibre 13.0.1 API (southWest/northEast) over the broken
flat property accessors.
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
|
|
MapLibre 13.0.1 only exposes northEast/southWest LatLng corners; the
flat latSouth/latNorth/lonWest/lonEast properties don't exist and cause
unresolved reference errors. Replace all occurrences in ParticleWindView
and MainActivity with the correct .southWest.latitude / .northEast.*
accessors.
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
|
|
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
|
|
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
|
|
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
|