Location search starts a new mission; shrink and center header bar

Ortssuche verschob bisher nur die Karte - der HTML-Demonstrator beginnt
bei einer Suche immer eine neue, nach dem gefundenen Ort benannte
Mission (setMissionFromPlace(), aufgerufen aus doSearch()). Vorherige
Aenderungen gehen dabei nicht verloren, da flushPendingAutosave()
(hier: CurrentMissionMetaNotifier._flushNow()) zuerst noch ausstehende
Autosaves schreibt.

Die Nominatim-Suche wurde dafuer aus MapSearchControls in einen
eigenstaendigen NominatimService extrahiert (analog WindService):
injectable http.Client, damit sich die Suche in Tests ohne echten
Netzwerkzugriff ueberschreiben laesst. CurrentMissionMetaNotifier bekam
dafuer startNewFromPlace() (startNew() intern darauf umgebaut, um
Duplikation zu vermeiden).

Kopfleiste von 86%/64% (Plan-/Fly-Modus) auf einheitlich 70%
Bildschirmbreite verkleinert - zentriert war sie durch Align(topCenter)
+ FractionallySizedBox bereits strukturell korrekt, wirkte bei der
vollen Breite aber unausgewogen.

Verifiziert: flutter analyze (0 issues), flutter test (64/64, davon 4
neue NominatimService-Tests und 1 neuer Widget-Test fuer den
Missions-Reset bei Ortssuche), manuell auf Pixel_10a-Emulator - Suche
nach "Rotterdam" setzt Fusszeile auf "Mission: Rotterdam" mit 0
Wegpunkten trotz zuvor bestehender Mission mit Wegpunkten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Constantin Leue
2026-07-28 15:22:36 +02:00
co-authored by Claude Sonnet 5
parent aaec7f0f72
commit 926d805156
7 changed files with 234 additions and 21 deletions
+62
View File
@@ -1,16 +1,23 @@
import 'dart:convert';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:dmc_app/app_mode/app_mode_cubit.dart';
import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart';
import 'package:dmc_app/services/database/app_database.dart' hide DroneProfile, Mission;
import 'package:dmc_app/services/geocoding/nominatim_service.dart';
import 'package:dmc_app/ui/app_shell.dart';
import 'package:dmc_app/ui/providers/current_mission_provider.dart';
import 'package:dmc_app/ui/providers/database_provider.dart';
import 'package:dmc_app/ui/providers/geocoding_provider.dart';
import 'package:dmc_app/ui/providers/mission_meta_provider.dart';
import 'package:dmc_app/ui/widgets/full_value_chart.dart';
import 'package:dmc_app/ui/widgets/halo_menu.dart';
import 'package:dmc_app/ui/widgets/header_wind_pill.dart';
@@ -442,6 +449,61 @@ void main() {
expect(find.text('Location not found'), findsNothing);
});
testWidgets(
'Ortssuche legt eine neue, nach dem Ort benannte Mission an',
(tester) async {
final client = MockClient((request) async => http.Response(
jsonEncode([
{
'lat': '51.9225',
'lon': '4.47917',
'display_name': 'Rotterdam, South Holland, Netherlands',
'address': {'city': 'Rotterdam'},
},
]),
200,
));
final container = ProviderContainer(
overrides: [
appDatabaseProvider.overrideWithValue(_testDatabase()),
nominatimServiceProvider
.overrideWithValue(NominatimService(client: client)),
],
);
addTearDown(container.dispose);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: BlocProvider(
create: (_) => AppModeCubit(),
child: const MaterialApp(home: AppShell()),
),
),
);
await tester.pump();
// Bestehenden Wegpunkt anlegen, damit sich pruefen laesst, dass die
// Suche die Mission wirklich neu beginnt statt nur die Karte zu
// verschieben (HTML-Demonstrator: setMissionFromPlace()).
await tester.tap(find.byType(ReticleButton));
await tester.pump();
expect(container.read(currentMissionProvider), hasLength(1));
await tester.enterText(find.byType(TextField), 'Rotterdam');
await tester.testTextInput.receiveAction(TextInputAction.search);
await tester.pump();
await tester.pump();
expect(container.read(currentMissionMetaProvider).name, 'Rotterdam');
expect(container.read(currentMissionMetaProvider).placeName, 'Rotterdam');
expect(container.read(currentMissionProvider), isEmpty);
// Autosave debounced ueber 800ms - abwarten, damit nach Testende kein
// Timer mehr aussteht (Doku 7.5).
await tester.pump(const Duration(milliseconds: 900));
});
testWidgets(
'Kameraposition bleibt beim Wechsel Plan -> Fly -> Plan erhalten',
(tester) async {