- RouteGeometry (lib/domain/mission/route_geometry.dart): physically
grounded flight path - straight segments + tangential arcs at each
course change, sized from the drone's minimum turn radius (doc 3.7/4.6),
replacing a naive spline that would suggest unrealistic turn radii.
Marks a vertex "bad" (turn angle >= 160°, tangent length exceeding 90%
of either adjacent leg, or exceeding the waypoint's catch radius) and a
leg "bad" when the required climb/descent rate exceeds the drone's
max climb/descent rate. Pure Dart, no Flutter dependency, so it stays
usable if the mission domain is ever split into its own package (4.22).
Wind-based turn-radius correction from the prototype isn't ported yet -
the wind system (doc 3.8) doesn't exist in the Flutter app.
- MissionMap now draws each RouteSegment as its own Polyline, colored red
when bad instead of a single plain white line; waypoint markers turn red
too when their vertex is bad.
- ValueWheel: vertical drag-to-adjust tape control (HTML prototype's
#altWheel/#spdWheel), custom-painted tick marks, blue center indicator,
gradient fade at top/bottom. Wired into PlanScreen on both screen edges
with Alt/Speed readouts.
- curAlt/curSpeed now live in PlanScreen state instead of fixed constants:
dropping a new waypoint uses whatever the wheels are currently set to;
entering editing mode on an existing waypoint loads its values into the
wheels; adjusting a wheel while editing writes live into that waypoint
(provider gained setAltitude/setSpeed to match the existing
moveWaypoint/toggleAction pattern).
Added dedicated unit tests for the geometry (empty list, straight line,
feasible 90° turn producing a line-arc-line segment sequence, an
infeasible near-180° turn, and an infeasible descent rate) rather than
relying on eyeballing it on the emulator - this is exactly the kind of
ported-math correctness that's hard to verify visually but easy to get
subtly wrong. All 11 tests (previous 6 + these 5) and flutter analyze
pass. Also manually verified the wheels on the Pixel_10a emulator: drag
changes the value and the on-screen readout in real time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full reticle interaction state machine (idle/snapped/editing), matching
the HTML prototype's mode variable:
- idle: reticle shows "+", tap drops a new waypoint at the map center.
- snapped: after any pan ends, if the map center lands within the
reticle's on-screen radius (converted to meters at the current
zoom/latitude) of an existing waypoint, the view auto-recenters onto it
and the reticle switches to a pencil icon. Tap to enter editing.
- editing: reticle shows a checkmark; panning the map now live-updates the
target waypoint's position (drag-to-reposition) instead of moving the
camera "away" from it, and the HaloMenu radial menu appears.
HaloMenu: three ring-wedge hit regions (custom ClipPath/CustomClipper,
angles/radii taken from the prototype's buildRingWedgePath) for
Loiter/Landing/Remove, with Material icons standing in for the
demonstrator's hand-drawn SVGs. Loiter/Landing toggle the waypoint's
action and drop back to snapped; Remove deletes it and returns to idle.
MissionMap now draws a straight white polyline connecting all waypoints
in order (the physically-grounded straight-line + fillet-arc geometry
from doc 3.7/4.6 is still open - this is a straight-line placeholder) plus
a dashed rubber-band preview from the last waypoint to the live map center
while idle.
Note on porting from Leaflet: flutter_map's MapController.move() emits
MapEventMove, not MapEventMoveEnd, so (unlike the prototype's panTo()) the
auto-recenter-on-snap doesn't re-trigger snap detection - no "autoPanning"
guard flag was needed.
Verified with flutter analyze, flutter test (three new tests: halo menu
open/close, remove, loiter toggle - the wedge tap tests had to target the
icon's exact center via tapAt(), since a wedge's bounding-box center falls
outside its actual donut-segment hit area), and a full manual pass on the
Pixel_10a emulator: idle -> add -> snap (auto-recenter confirmed) -> edit
-> drag-reposition (confirmed via re-snap after further panning showing
the moved position, not the original one) -> halo remove -> back to idle,
plus the connecting line rendering correctly between two waypoints set
far apart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ReticleButton: fixed-at-screen-center "+" circle (HTML prototype:
#centerCluster/#reticleBtn), tapping it drops a waypoint at the current
map center.
- CurrentMissionNotifier (Riverpod): holds the waypoint list for the
mission being edited. Reactive single-value state per doc 4.16, so
Riverpod rather than a new Bloc/Cubit.
- MissionMap now renders each waypoint as a CircleLayer marker (white
border, dark fill, matching the prototype's L.circleMarker styling).
- PlanScreen owns the MapController needed to read the camera center on
tap, and wires the reticle to the provider.
Default altitude/speed/catch-radius (60m / 15 m/s / 60m) are hardcoded to
the T1 Ranger default profile values from the prototype for now - TODO
once drone profile settings (doc 3.5) exist.
Verified with flutter analyze, flutter test (incl. a new test covering the
add-waypoint path), and a real run on the Pixel_10a emulator: panned the
map and dropped two waypoints, both markers stayed correctly georeferenced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- TopModeBar widget: persistent pill-shaped header (HTML prototype:
#topBarWrap/#editModeBtn/#flyModeBtn) tinted in the active mode's color,
with the inactive mode offered as a solid button in the corner to switch
into it.
- AppShell now provides a single shared Scaffold + Stack, overlaying
TopModeBar on top of whichever screen (Plan/Fly) is active, instead of
each screen owning its own Scaffold.
- Wired the Fly button to AppModeCubit.toFly(), which enforces the
Ready-to-Fly gate (doc 4.5). Since upload/verify isn't wired up yet, the
gate is never satisfied yet, so tapping Fly now shows a SnackBar instead
of throwing an uncaught StateError.
- Extended the widget test to cover both the bar's presence and the
gate-rejection path (was the source of a real bug: an initial
negative-margin Container hack for the "bleed into the corner" look
violated Container's margin.isNonNegative assertion and crashed the
whole tree - fixed with padding instead).
Verified with flutter analyze, flutter test, and a real run on the
Pixel_10a emulator (visually matches the prototype screenshots in design/,
Fly-tap correctly shows the gate message without crashing or switching mode).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Lock orientation to landscape (sensorLandscape in the manifest,
SystemChrome.setPreferredOrientations in main.dart) and enable immersive
fullscreen, matching the prototype's full-bleed map UI (doc 7.7).
- Add MissionMap widget: flutter_map + standard OSM raster tiles, same
default center/zoom as the HTML prototype (Amsterdam, zoom 16), OSM
attribution bottom-left. PlanScreen now renders it full-screen instead of
the placeholder text.
- Add the release-manifest INTERNET permission (previously only present in
the debug manifest, which would have silently broken tile loading and any
future network calls in release builds).
- Update the smoke test to check for the FlutterMap widget instead of the
removed placeholder text.
Verified with flutter analyze, flutter test, and a real run on the
Pixel_10a emulator (fullscreen landscape confirmed, tiles loading).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Disable Kotlin incremental compilation (kotlin.incremental=false): the
project lives on D: while Gradle's Kotlin daemon caches default to C:,
which crashes with "this and base files have different roots" when
closing incremental caches.
- Drop flutter_map_tile_caching for now: it pins objectbox_flutter_libs
^4.1.0, which fails checkReleaseAarMetadata against newer AndroidX
transitive deps (needs compileSdk 34+, itself built against android-31).
Not used in code yet, so deferred rather than forcing an unverified
dependency_override.
- Add web/ platform for fast local UI iteration without an emulator.
- Ignore .claude/ (Claude Code session state, not project content).
Verified: flutter analyze, flutter test, and a real release build running
on a newly created Pixel_10a-profile Android emulator (API 36).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>