| Age | Commit message (Collapse) | Author |
|
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
|
|
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
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
|
|
subtraction overflow, filtering every tack
|
|
from VoiceLogFragment
|
|
from VoiceLogFragment
|
|
fixes, dim past tracks
* Fix tack detection and convert track detail to embedded-map fragment
TackDetector: revert STABILITY_MAX to 30° (was tightened too far to 20°),
add 120s start-skip to filter GPS cold-start false positives, keep 45s
minimum gap between tacks.
TrackDetailSheet: convert from BottomSheetDialogFragment to a regular
Fragment with an embedded MapLibreMap in the top half. This fixes the
map centering issue (the embedded map is always sized to its own frame),
eliminates accidental dismissal when dragging the map, and makes the
view self-contained — back navigation restores the track list via the
fragment back stack. Tack markers rendered on the embedded map; tapping
a log event pans the embedded map directly.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
* Fix XML conflict markers left in layout_track_detail_sheet.xml
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
* Fix Kotlin conflict markers left in TrackDetailSheet.kt
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
* Fix compile errors on feature branch
- SavedTracksFragment/VoiceLogFragment: remove references to SafState,
safState, launchSafPicker, layout_empty, btn_setup_storage, and
layout_log_entry — none of these exist on this branch
- TrackStorage: add missing onCancellation lambda to cont.resume()
- build.gradle: enable buildConfig generation (fixes unresolved BuildConfig)
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
* Dim past tracks: dark navy 0.12 opacity, solid lines
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
|
past track opacity
- Tack detector: STABILITY_MAX=30, START_SKIP_MS=120s cold-start filter (feature branch)
- TrackDetailSheet: embedded Fragment with MapView, back button pops stack (feature branch)
- layout_track_detail_sheet: MapView top half, detail panel bottom half (feature branch)
- MapHandler past track dim opacity: 0.12 (down from 0.22)
- SavedTracksFragment: fragment transaction to TrackDetailSheet + SAF setup (both)
- VoiceLogFragment: inline track list, logViewModel refs fixed (main)
|
|
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
- SavedTracksFragment/VoiceLogFragment: remove references to SafState,
safState, launchSafPicker, layout_empty, btn_setup_storage, and
layout_log_entry — none of these exist on this branch
- TrackStorage: add missing onCancellation lambda to cont.resume()
- build.gradle: enable buildConfig generation (fixes unresolved BuildConfig)
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
TackDetector: revert STABILITY_MAX to 30° (was tightened too far to 20°),
add 120s start-skip to filter GPS cold-start false positives, keep 45s
minimum gap between tacks.
TrackDetailSheet: convert from BottomSheetDialogFragment to a regular
Fragment with an embedded MapLibreMap in the top half. This fixes the
map centering issue (the embedded map is always sized to its own frame),
eliminates accidental dismissal when dragging the map, and makes the
view self-contained — back navigation restores the track list via the
fragment back stack. Tack markers rendered on the embedded map; tapping
a log event pans the embedded map directly.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Sheet: set isDraggable=false so scrolling the event list doesn't push
the sheet up over the map. Top 50% stays map, bottom 50% stays list.
Tack detector: replaced 5-index skip (~5 s at 1 Hz) with a 45-second
timestamp gap. Also tightened STABILITY_MAX from 30° to 20° to reduce
false positives from GPS heading noise.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Kotlin inferred Feature|FeatureCollection as their common supertype,
which didn't match any single setGeoJson overload. Split into two
explicit branches so each call site has an unambiguous type.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
Removed the 'Saved Tracks' button that opened a separate overlay.
Tracks now appear directly on the Log screen in a RecyclerView below
the Generate Trip Report button. Tapping a track hides the overlay and
opens the existing TrackDetailSheet bottom sheet over the map.
The log entry section (text field, mic, camera, save/clear) is hidden
with visibility=gone when not recording and shown when a recording is
in progress. The storage-setup prompt is now surfaced here too.
SavedTrackAdapter and DATE_FMT widened to internal so VoiceLogFragment
can reuse them directly. SavedTracksFragment kept for the GPX import flow.
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
|
|
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>
|
|
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>
|