| Age | Commit message (Collapse) | Author |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 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>
|
|
The instrument sheet CardView has cardElevation=16dp which was
rendering on top of the FABs (default ~6dp elevation). Set
app:elevation=20dp on both FABs so they always appear above the sheet.
Also reverts oversized marginBottom back to standard 16dp all-round
now that elevation stacking is correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
anchorGravity=top centers the FAB on the sheet edge, leaving half
the button occluded. Increase marginBottom to 44dp (28dp to clear
the FAB radius + 16dp gap) so both buttons sit fully above the sheet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Both fab_mob and fab_record_track now anchor to instrument_bottom_sheet
so they sit above the sheet peek zone rather than being occluded by
the sheet's 16dp elevation. Resolves stash conflict in settings.local.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
TrackRepository.addPoint() now returns Boolean (true if point was
added). MainViewModel.addGpsPoint() only updates _trackPoints StateFlow
when a point is actually appended — eliminates ~3,600 no-op flow
emissions per hour when not recording.
MainActivity: loadedStyle promoted from nullable field to
MutableStateFlow<Style?>; trackPoints observer uses filterNotNull +
combine so no track points are silently dropped if the style loads
after the first GPS fix.
Smoke tests: replaced 11× ActivityScenario.launch().use{} with a
single @get:Rule ActivityScenarioRule — same isolation, less
boilerplate.
CI: removed redundant app-debug artifact upload (app-debug.apk is
already included inside the test-apks artifact).
Removed stale/placeholder comments from MainActivity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
CI — build job now uploads both APKs as the 'test-apks' artifact.
smoke-test job downloads them and passes -x assembleDebug
-x assembleDebugAndroidTest to skip recompilation (~4 min saved).
Test results uploaded as 'smoke-test-results' artifact on every run.
Smoke tests expanded from 1 → 11 tests covering:
- MainActivity launches without crash
- All 4 bottom-nav tabs are displayed
- Safety tab: Safety Dashboard, ACTIVATE MOB, ANCHOR WATCH visible
- Log tab: voice-log mic FAB visible
- Instruments tab: bottom sheet displayed
- Map tab: returns from overlay, mapView visible
- MOB FAB: always visible, visible on Safety tab
- Record Track FAB: displayed, toggles to Stop Recording, toggles back
MainActivity: moved isRecording observer to initializeUI() so the
FAB content description updates without requiring GPS permission
(needed for emulator tests that run without location permission).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- TrackRepository + TrackPoint wired into MainViewModel:
isRecording/trackPoints StateFlows, startTrack/stopTrack/addGpsPoint
- MapHandler.updateTrackLayer(): lazily initialises a red LineLayer
and updates GeoJSON polyline from List<TrackPoint>
- fab_record_track FAB in activity_main.xml (top|end of bottom nav);
icon toggles between ic_track_record and ic_close while recording
- MainActivity feeds every GPS fix into viewModel.addGpsPoint() and
observes trackPoints to redraw the polyline in real time
- ic_track_record.xml vector drawable (red record dot)
- 8 TrackRepositoryTest tests all GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Migrate kapt → KSP (Kotlin 2.0 + kapt is broken; KSP is the supported path)
- Fix duplicate onResume() override in MainActivity
- Fix wrong package imports: com.example.androidapp.data.model → org.terst.nav.data.model
across GribFileManager, GribStalenessChecker, SatelliteGribDownloader,
LogbookFormatter, LogbookPdfExporter, IsochroneRouter, AnchorWatchHandler
- Create missing SensorData, ApparentWind, TrueWindData, TrueWindCalculator classes
- Inline missing ScopeCalculator formula (Pythagorean) in AnchorWatchState
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- .gitignore: exclude agent artifacts (.claudomator-env, .agent-home/, crash-logs/)
- scripts/pull-crash-logs: download Crashlytics crash reports via Firebase CLI
- .claude/settings.local.json: add Android SDK/emulator/adb permission rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Extend LocationService's source-selection policy with a quality-aware
"marginal staleness" zone between the primary and a new extended
staleness threshold (default 10 s):
1. Fresh NMEA (≤ primary threshold, 5 s) → always prefer NMEA
2. Marginally stale NMEA (5–10 s) → prefer NMEA only when
GpsPosition.accuracyMeters is strictly better than Android's;
fall back to Android conservatively when accuracy data is absent
3. Very stale NMEA (> 10 s) → always prefer Android
4. Only one source available → use it regardless of age
Changes:
- GpsPosition: add nullable accuracyMeters field (default null, no
breaking change to existing callers)
- LocationService: add nmeaExtendedThresholdMs constructor parameter;
recomputeBestPosition() now implements three-tier logic; extract
GpsPosition.hasStrictlyBetterAccuracyThan() helper
- LocationServiceTest: expose nmeaExtendedThresholdMs in fusionService
helper; add posWithAccuracy helper; add 4 new test cases covering
accuracy-based NMEA preference, worse-accuracy fallback, no-accuracy
conservative fallback, and very-stale unconditional fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds priority-based selection between NMEA GPS (dedicated marine GPS,
higher priority) and Android system GPS (fallback) within LocationService.
Selection policy:
1. Prefer NMEA when its most recent fix is fresh (≤ nmeaStalenessThresholdMs, default 5 s)
2. Fall back to Android GPS when NMEA is stale
3. Use stale NMEA only when Android has never reported a fix
4. bestPosition is null until at least one source reports
New public API:
- GpsSource enum (NONE, NMEA, ANDROID)
- LocationService.updateNmeaGps(GpsPosition)
- LocationService.updateAndroidGps(GpsPosition)
- LocationService.bestPosition: StateFlow<GpsPosition?>
- LocationService.activeGpsSource: StateFlow<GpsSource>
- Injectable clockMs parameter for deterministic unit tests
Adds 7 unit tests covering: no-data state, fresh NMEA priority,
stale NMEA fallback, only-NMEA/only-Android scenarios, exact-threshold
edge case, and NMEA recovery after Android takeover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Per COMPONENT_DESIGN.md Section 4.6, the MOB navigation view must display
wind and current conditions at the time of the event.
- MobEvent: add nullable windSpeedKt, windDirectionDeg, currentSpeedKt,
currentDirectionDeg fields captured at the exact moment of activation
- MobAlarmManager.activate(): accept optional wind/current params and
forward them into MobEvent (defaults to null for backward compatibility)
- LocationService (new): aggregates live SensorData (resolves true wind via
TrueWindCalculator) and marine-forecast current conditions; snapshot()
provides a point-in-time EnvironmentalSnapshot for safety-critical logging
- MobAlarmManagerTest: add tests for wind/current storage and null defaults
- LocationServiceTest (new): covers snapshot, true-wind resolution,
current-condition updates, and the latestSensor flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add AnchorWatchState with calculateRecommendedWatchCircleRadius, which
uses ScopeCalculator.watchCircleRadius (Pythagorean scope formula) and
falls back to rode length when geometry is invalid
- Add AnchorWatchHandler Fragment with EditText inputs for Depth (m) and
Rode Out (m); updates suggested watch circle radius live via TextWatcher
- Add fragment_anchor_watch.xml layout
- Wire AnchorWatchHandler into bottom navigation (MainActivity + menu)
- Add AnchorWatchStateTest covering valid geometry, short-rode fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Implements weather data download over Iridium satellite links:
- GribParameter enum with SATELLITE_MINIMAL set (wind + pressure only)
- SatelliteDownloadRequest data class (region, params, forecast hours, resolution)
- SatelliteGribDownloader: size/time estimation, abort-on-oversized, pluggable fetcher
- 8 unit tests covering estimation scaling, minimal param set, and download outcomes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add GribRegion, GribFile data models and GribFileManager interface
- Add InMemoryGribFileManager for testing and default use
- Add GribStalenessChecker with FreshnessResult sealed class (Fresh/Stale/NoData)
- Integrate weatherStaleness StateFlow into MainViewModel (checked after loadWeather)
- Add yellow staleness banner TextView to fragment_map.xml
- Wire staleness banner in MapFragment (shown on Stale, hidden on Fresh/NoData)
- Add GribStalenessCheckerTest (4 TDD tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- LogbookEntry data class: timestampMs, lat/lon, SOG, COG, wind, baro, depth, event/notes
- LogbookFormatter: UTC time, position (deg/dec-min), 16-pt compass, row/page builders
- LogbookPdfExporter: landscape A4 PDF via android.graphics.pdf.PdfDocument with column headers,
alternating row shading, and table border
- 20 unit tests covering all formatting helpers and data model behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
Implement offline harmonic tide prediction as specified in COMPONENT_DESIGN.md:
- TideConstituent: name, speedDegPerHour, amplitudeMeters, phaseDeg
- TidePrediction: timestampMs, heightMeters
- TideStation: id, name, lat, lon, datumOffsetMeters, constituents
- HarmonicTideCalculator: predictHeight(), predictRange(), findHighLow()
Formula: h(t) = Z0 + Σ [ Hi × cos( ωi × (t − t0) − φi ) ]
- 15 unit tests covering all calculation paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- GpsPosition: latitude, longitude, sog (knots), cog (degrees true), timestampMs
- NmeaParser.parseRmc: handles GP/GN talker IDs, void status, malformed input
- SOG/COG default to 0.0 when fields absent; S/W coords are negative
- 13 unit tests: GpsPositionTest (2), NmeaParserTest (11) — all GREEN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add FOREGROUND_SERVICE_LOCATION permission (required on Android 14+
when foregroundServiceType="location" is declared)
- Defer startServices() to onResume() via pendingServiceStart flag so
startForegroundService() is never called while app is backgrounded
(fixes ForegroundServiceStartNotAllowedException on Android 12+)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Caused a fatal InflateException crash on app launch. Android requires
layout_width and layout_height on <include> tags explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
InstrumentLabel and InstrumentPrimaryValue were missing layout dimension
attributes, causing InflateException crash on startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Make InstrumentHandler.labelTrend and barometerTrendView nullable
- Remove anchorWatchHandler init block referencing non-existent view IDs
- Fix state.radiusM -> state.watchCircleRadiusMeters
- Add bottom_nav_weather_menu.xml with nav_forecast; point WeatherActivity at it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace FAB-based navigation with a 4-tab BottomNavigationView (Map,
Instruments, Log, Safety). Instruments use a collapsible bottom sheet;
Log and Safety display as full-screen overlay fragments.
- Add SafetyFragment with MOB and Anchor Watch controls
- Add DocFragment for in-app markdown help (Markwon: core, tables, images)
- Add layout_instruments_sheet with 3x3 instrument grid and PolarDiagramView
- Add fragment_safety and fragment_doc layouts
- Add vector drawables: ic_map, ic_instruments, ic_log, ic_safety, ic_close
- Update activity_main.xml to CoordinatorLayout with bottom sheet + overlay
- Fix: set isHideable=true before STATE_HIDDEN to avoid silent no-op from
behavior_hideable=false default; restore false on Map/Instruments tabs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
background permission check in tests
|
|
|
|
|
|
- Extracted MOB, Instruments, Map, and Anchor Watch logic from MainActivity into dedicated handlers.
- Refactored LocationService to use a standalone MockTidalCurrentGenerator.
- Removed legacy 'kotlin_old', 'res_old', and 'temp' directories.
- Added KDoc documentation to core components and handlers.
- Integrated JUnit 5 dependencies and configured the test runner.
- Verified all changes with successful unit test execution.
|
|
BitmapFactory.decodeResource returns null for XML vector drawables.
Passing null to MapLibre's style.addImage caused a NPE that propagated
through JNI as PendingJavaException, crashing the app on every launch.
Fix uses ContextCompat.getDrawable + Canvas rasterization, matching
the pattern already used in setupTidalCurrentMapLayers.
Also upgrades MapLibre Android SDK from 11.5.1 to 13.0.1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
errors
map_orig/MapFragment.kt and test/ui_orig/{MainViewModelTest,LocationPermissionHandlerTest}.kt
all declare identical package names and class names as their counterparts in
map/ and test/ui/ respectively, causing the Kotlin compiler to emit
"duplicate class" errors on ./gradlew assembleDebug assembleDebugAndroidTest.
Deleted the _orig copies; the real implementations are in map/ and test/ui/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
modelClass.kotlin.viewModelScope called viewModelScope on KClass<T>
rather than a ViewModel instance. Replace with CoroutineScope(Dispatchers.IO
+ SupervisorJob()) which is valid at factory creation time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Posts workflow_run events to the claudomator server on completion of
both the build and smoke-test jobs (success or failure).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|