Auto-fetch wind, drop manual fetch/validate buttons, fix it in fly mode

Redesigns the waypoint list panel to match the drone-status/missions
panels: no title bar, close button floats over the tab row, tabs get
more vertical padding.

Removes the "Fetch wind" and "Validate" header buttons along with the
now-fully-orphaned WindValidatePanel screen and its WindService
support code (fetchValidation, WindValidationResult and friends) -
nothing else referenced them.

Wind is now fetched automatically instead: HeaderWindNotifier gains
ensurePerWaypointWind(), called when the waypoint list panel opens and
when the header pill's per-waypoint toggle switches on. A lat/lon
signature of the waypoint list tracks whether the fetched data is
still current, so a ref.listen(currentMissionProvider) in the same
notifier refetches only when the list itself changes (position added/
removed/moved) - not on every altitude/speed edit. Waypoints that
already carry wind data (e.g. loaded from a saved mission) short-
circuit the check instead of re-fetching pointlessly.

Also wires per-waypoint wind markers into the fly-mode map, which
never had them (plan_screen.dart built windMarkers; fly_screen.dart
just never did) - the reported "doesn't work in fly mode" bug.
windMarkerWidth() moved from a plan_screen.dart-private method to
wind_marker_pill.dart so both screens can share it.

Test containers in widget_test.dart now override windServiceProvider
with a MockClient so the new auto-fetch trigger can't reach the real
network during tests.
This commit is contained in:
Constantin Leue
2026-08-09 22:26:23 +02:00
parent 6b7977eca1
commit eca1c647b3
10 changed files with 368 additions and 570 deletions
-75
View File
@@ -142,79 +142,4 @@ void main() {
expect(called, isFalse);
});
});
group('WindService.fetchValidation', () {
test('markiert unvollstaendige/unter Boden liegende Druckflaechen als excluded',
() async {
final client = MockClient((request) async {
if (_isPressureRequest(request)) {
return _json({
'elevation': 50.0,
'hourly': {
'time': ['2024-06-01T12:00'],
'wind_speed_1000hPa': [4.0],
'wind_direction_1000hPa': [300.0],
'geopotential_height_1000hPa': [5.0], // unter Bodenhoehe 50
'wind_speed_975hPa': [12.0],
'wind_direction_975hPa': [300.0],
'geopotential_height_975hPa': [800.0],
},
});
}
return _json({
'elevation': 50.0,
'hourly': {
'time': ['2024-06-01T12:00'],
'wind_speed_10m': [1.0],
'wind_direction_10m': [100.0],
'wind_speed_80m': [9.0],
'wind_direction_80m': [250.0],
'wind_speed_120m': [10.0],
'wind_direction_120m': [260.0],
'wind_speed_180m': [11.0],
'wind_direction_180m': [270.0],
'wind_gusts_10m': [12.5],
},
});
});
final service = WindService(client: client);
final result = await service.fetchValidation(52.0, 4.0);
expect(result.elevationM, closeTo(50.0, 0.001));
expect(result.fixedRequestFailed, isFalse);
expect(result.pressureRequestFailed, isFalse);
expect(result.gustMs, closeTo(12.5, 0.001));
final level1000 =
result.pressureReadings.firstWhere((r) => r.pressureHpa == 1000);
expect(level1000.excluded, isTrue,
reason: 'liegt unter der Bodenhoehe');
final level975 =
result.pressureReadings.firstWhere((r) => r.pressureHpa == 975);
expect(level975.excluded, isFalse);
final level950 =
result.pressureReadings.firstWhere((r) => r.pressureHpa == 950);
expect(level950.excluded, isTrue,
reason: 'keine Daten fuer diese Druckflaeche geliefert');
expect(result.interpolatedAt120, isNotNull);
});
test('meldet fehlgeschlagenen Request statt zu crashen', () async {
final client = MockClient((request) async {
if (_isPressureRequest(request)) return http.Response('', 500);
return http.Response('', 500);
});
final service = WindService(client: client);
final result = await service.fetchValidation(52.0, 4.0);
expect(result.fixedRequestFailed, isTrue);
expect(result.pressureRequestFailed, isTrue);
expect(result.interpolatedAt120, isNull);
});
});
}