diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index 74403a7..74be2b9 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // App laeuft ausschliesslich im Landscape-Vollbild (Prototyp-Referenz: + // Kartenansicht nutzt den gesamten Bildschirm ohne System-Chrome). + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + runApp(const DmcApp()); } diff --git a/app/lib/ui/screens/plan/plan_screen.dart b/app/lib/ui/screens/plan/plan_screen.dart index fd98865..31505a0 100644 --- a/app/lib/ui/screens/plan/plan_screen.dart +++ b/app/lib/ui/screens/plan/plan_screen.dart @@ -1,15 +1,18 @@ import 'package:flutter/material.dart'; +import '../../widgets/mission_map.dart'; + /// Missionsplanung: Karte + Wegpunktliste (Architektur-Doku 3.6/3.7). -/// Kartenintegration (flutter_map) und Fillet-Geometrie folgen als naechster -/// Schritt - dies ist bewusst nur der Screen-Platzhalter. +/// Top-/Bottom-Bar-Overlays (Modus-Umschalter, Draeder, Wegpunktliste) folgen +/// als naechster Schritt - dies ist bewusst erst die Vollbild-Karte. class PlanScreen extends StatelessWidget { const PlanScreen({super.key}); @override Widget build(BuildContext context) { return const Scaffold( - body: Center(child: Text('Plan')), + extendBodyBehindAppBar: true, + body: MissionMap(), ); } } diff --git a/app/lib/ui/widgets/mission_map.dart b/app/lib/ui/widgets/mission_map.dart new file mode 100644 index 0000000..654f8ec --- /dev/null +++ b/app/lib/ui/widgets/mission_map.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Vollbild-OSM-Karte, entspricht dem Leaflet-`#map` im HTML-Demonstrator: +/// randlos, ohne Zoom-Buttons (Pinch-to-Zoom/Pan reicht), Standard-OSM-Tiles. +class MissionMap extends StatelessWidget { + const MissionMap({super.key, this.mapController}); + + final MapController? mapController; + + // Default-Zentrum/Zoom uebernommen aus dem HTML-Demonstrator + // (Drone Mission Control.html: L.map(...).setView([52.3676, 4.9041], 16)). + static const _initialCenter = LatLng(52.3676, 4.9041); + static const _initialZoom = 16.0; + + @override + Widget build(BuildContext context) { + return FlutterMap( + mapController: mapController, + options: const MapOptions( + initialCenter: _initialCenter, + initialZoom: _initialZoom, + minZoom: 3, + maxZoom: 19, + ), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'de.dmc.dmc_app', + maxZoom: 19, + ), + SimpleAttributionWidget( + alignment: Alignment.bottomLeft, + source: const Text('OpenStreetMap contributors'), + onTap: () => + launchUrl(Uri.parse('https://www.openstreetmap.org/copyright')), + ), + ], + ); + } +} diff --git a/app/pubspec.lock b/app/pubspec.lock index c58ee62..a040fca 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -917,6 +917,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.9" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + url: "https://pub.dev" + source: hosted + version: "6.3.32" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" uuid: dependency: transitive description: @@ -1015,4 +1079,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.12.2 <4.0.0" - flutter: ">=3.38.4" + flutter: ">=3.44.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 2ba1f4e..4da2e70 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: flutter_bloc: ^9.1.1 flutter_riverpod: ^3.4.1 go_router: ^17.3.0 + url_launcher: ^6.3.2 dev_dependencies: flutter_test: diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart index ca17604..72af05e 100644 --- a/app/test/widget_test.dart +++ b/app/test/widget_test.dart @@ -1,5 +1,6 @@ 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'; @@ -7,7 +8,7 @@ import 'package:dmc_app/app_mode/app_mode_cubit.dart'; import 'package:dmc_app/ui/app_shell.dart'; void main() { - testWidgets('AppShell zeigt Plan-Screen im Startzustand', (tester) async { + testWidgets('AppShell zeigt die Karte im Plan-Startzustand', (tester) async { await tester.pumpWidget( ProviderScope( child: BlocProvider( @@ -16,7 +17,10 @@ void main() { ), ), ); + // Nur ein Frame pumpen statt pumpAndSettle: TileLayer laedt echte + // Netzwerk-Kacheln, die in der Testumgebung nie ankommen. + await tester.pump(); - expect(find.text('Plan'), findsOneWidget); + expect(find.byType(FlutterMap), findsOneWidget); }); }