From 346fcc2183261c39a1cf78ca14c512eb667486a7 Mon Sep 17 00:00:00 2001 From: Constantin Leue <77660791+Consti3D@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:28:25 +0200 Subject: [PATCH] Add Flutter project skeleton (Android-only MVP) Five-layer architecture per DMC_Architektur_und_Design.md: FlightControllerLink abstraction (Mock + MSP stub), protocol-neutral mission domain layer, App-Mode state machine with Ready-to-Fly gate, Drift persistence schema, and placeholder UI screens wired via go_router. App icon generated from design/Y2_pink_dark.svg. Verified with flutter analyze and flutter test. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 83 ++ app/.gitignore | 45 + app/.metadata | 30 + app/README.md | 17 + app/analysis_options.yaml | 28 + app/android/.gitignore | 14 + app/android/app/build.gradle.kts | 45 + app/android/app/src/debug/AndroidManifest.xml | 7 + app/android/app/src/main/AndroidManifest.xml | 45 + .../kotlin/com/dmc/dmc_app/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4426 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2763 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5651 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8665 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 11693 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + app/android/build.gradle.kts | 24 + app/android/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.properties | 5 + app/android/settings.gradle.kts | 26 + app/assets/icon/icon.png | Bin 0 -> 25425 bytes app/lib/app_mode/app_mode_cubit.dart | 65 + app/lib/domain/mission/mission.dart | 24 + .../domain/waypoint/flat_waypoint_list.dart | 61 + app/lib/main.dart | 55 + app/lib/services/database/app_database.dart | 39 + app/lib/services/database/app_database.g.dart | 708 +++++++++++ app/lib/services/mission_sync_service.dart | 26 + app/lib/services/telemetry_service.dart | 15 + app/lib/transport/flight_controller_link.dart | 57 + .../mock/mock_flight_controller_link.dart | 71 ++ .../msp/msp_flight_controller_link.dart | 39 + app/lib/ui/app_shell.dart | 19 + app/lib/ui/dmc_colors.dart | 14 + app/lib/ui/screens/fly/fly_screen.dart | 15 + .../ui/screens/library/library_screen.dart | 15 + app/lib/ui/screens/plan/plan_screen.dart | 15 + .../ui/screens/settings/settings_screen.dart | 15 + app/pubspec.lock | 1050 +++++++++++++++++ app/pubspec.yaml | 112 ++ app/test/widget_test.dart | 22 + 45 files changed, 2884 insertions(+) create mode 100644 CLAUDE.md create mode 100644 app/.gitignore create mode 100644 app/.metadata create mode 100644 app/README.md create mode 100644 app/analysis_options.yaml create mode 100644 app/android/.gitignore create mode 100644 app/android/app/build.gradle.kts create mode 100644 app/android/app/src/debug/AndroidManifest.xml create mode 100644 app/android/app/src/main/AndroidManifest.xml create mode 100644 app/android/app/src/main/kotlin/com/dmc/dmc_app/MainActivity.kt create mode 100644 app/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 app/android/app/src/main/res/drawable/launch_background.xml create mode 100644 app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/android/app/src/main/res/values-night/styles.xml create mode 100644 app/android/app/src/main/res/values/styles.xml create mode 100644 app/android/app/src/profile/AndroidManifest.xml create mode 100644 app/android/build.gradle.kts create mode 100644 app/android/gradle.properties create mode 100644 app/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 app/android/settings.gradle.kts create mode 100644 app/assets/icon/icon.png create mode 100644 app/lib/app_mode/app_mode_cubit.dart create mode 100644 app/lib/domain/mission/mission.dart create mode 100644 app/lib/domain/waypoint/flat_waypoint_list.dart create mode 100644 app/lib/main.dart create mode 100644 app/lib/services/database/app_database.dart create mode 100644 app/lib/services/database/app_database.g.dart create mode 100644 app/lib/services/mission_sync_service.dart create mode 100644 app/lib/services/telemetry_service.dart create mode 100644 app/lib/transport/flight_controller_link.dart create mode 100644 app/lib/transport/mock/mock_flight_controller_link.dart create mode 100644 app/lib/transport/msp/msp_flight_controller_link.dart create mode 100644 app/lib/ui/app_shell.dart create mode 100644 app/lib/ui/dmc_colors.dart create mode 100644 app/lib/ui/screens/fly/fly_screen.dart create mode 100644 app/lib/ui/screens/library/library_screen.dart create mode 100644 app/lib/ui/screens/plan/plan_screen.dart create mode 100644 app/lib/ui/screens/settings/settings_screen.dart create mode 100644 app/pubspec.lock create mode 100644 app/pubspec.yaml create mode 100644 app/test/widget_test.dart diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6dd053e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# Drone Mission Control (DMC) + +Mobile App zur Missionsplanung und Flugsteuerung für einen Fixed-Wing-Drohnen-Aufbau +(Heewing T1 Ranger, iNAV-Firmware). Wegpunkt-Missionen, Live-Fly-Modus, Point-and-Fly +(PnF)-Einzelziel-Anflug. + +**Vollständige Architektur- und Design-Entscheidungen:** [DMC_Architektur_und_Design.md](DMC_Architektur_und_Design.md) +— vor jeder größeren Änderung lesen, insbesondere Abschnitt 4 (Entscheidungen inkl. +Begründung), Abschnitt 5 (verworfene Alternativen) und Abschnitt 6 (offene Fragen). + +## Sprachregelung + +Konversation und Dokumentation auf Deutsch, Code und UI-Texte auf Englisch. + +## Projektstruktur + +- `Drone Mission Control.html` — lauffähiger Vanilla-JS/Leaflet-Demonstrator, dient als + Referenz für UI-Verhalten und bereits gelöste Probleme (Wind-Interpolation, + Terrain-Sampling, Fillet-Geometrie, swipe-adjustierbare Wegpunkt-Chips, ...). Bei + Unklarheiten über gewünschtes Verhalten zuerst hier nachsehen, bevor neu entschieden + wird. +- `design/` — Logo-Assets (SVG-Varianten) und Icon-Sketches. Aktuell verwendetes + App-Icon: `design/Y2_pink_dark.svg`. +- `app/` — Flutter-Zielarchitektur (Einzelpaket, kein Melos-Monorepo). Siehe unten. +- `test/` — Altlast/Sandbox, nicht Teil der aktiven Entwicklung. + +## Flutter-App (`app/`) + +Fünf-Schichten-Architektur (Doku Abschnitt 2): + +``` +lib/ + ui/ Screens (plan, fly, settings, library) + geteilte Widgets + app_mode/ App-Mode State Machine (Plan → Connect → Upload → Fly) + domain/ Mission-Domain-Layer (Strategy Pattern: TrackMission, ...) + + protokollneutrale FlatWaypointList + services/ MissionSyncService, TelemetryService, Drift-Datenbank + transport/ FlightControllerLink-Abstraktion (msp/, mavlink/, mock/) +``` + +**MVP-Scope (Doku 4.1):** nur Android, nur iNAV via MSP, nur WiFi/TCP. MAVLink, +USB-Seriell und iOS sind bewusst zurückgestellt. + +**Kernprinzip:** Die Domain-Schicht kennt nur `FlatWaypointList`, protokollneutral. +Divergenz zu MSP/MAVLink findet ausschließlich im Encoder statt. `FlightControllerLink` +ist die zentrale Abstraktion; `MockFlightControllerLink` erlaubt UI-Arbeit ohne +Hardware. `MspFlightControllerLink` ist aktuell nur ein Gerüst (`UnimplementedError`) +— die eigentliche MSP-Protokoll-Implementierung (Framing, `MSP_SET_WP`, +`MSP_NAV_STATUS`) ist noch offen. + +**Ready-to-Fly-Gate (Doku 2.2/4.5):** Upload + verifiziert + disarmed. Wird in +`AppModeCubit.toFly()` technisch erzwungen (wirft `StateError`), nicht nur als +UI-Konvention behandelt. + +**Persistenz:** Drift (SQLite), nicht Hive — siehe Begründung 4.15. Schema in +`lib/services/database/app_database.dart`. Nach Schema-Änderungen: +``` +dart run build_runner build +``` +(im `app/`-Verzeichnis; `--delete-conflicting-outputs` wird von der installierten +build_runner-Version ignoriert und ist nicht nötig). + +**Entschiedene Pakete** (siehe Doku Abschnitt 8): `flutter_map` + `flutter_map_tile_caching` +(nicht `google_maps_flutter`), `drift`, `flutter_bloc` + `flutter_riverpod` (kombiniert: +Bloc für die App-Mode State Machine, Riverpod für reaktive Einzelwerte wie Telemetrie), +`go_router`. + +**Noch nicht umgesetzt / bewusst zurückgestellt:** +- Eigener MSP-Encoder/Decoder in Dart (4.19) — kein fertiges Paket verfügbar. +- Telemetrie-Verarbeitung in eigenem Isolate (4.21) — erst sinnvoll mit echtem + Protokoll-Adapter zu verifizieren. +- `SurveyAreaMission`/`SearchAreaMission` (Strategy-Pattern vorgesehen, Details offen). +- Foreground Service (Android) für Hintergrund-Telemetrie (4.20). +- `flutter_launcher_icons` ist konfiguriert für Android; iOS ist aus MVP-Gründen + deaktiviert (`ios: false` in der `flutter_launcher_icons`-Sektion der `pubspec.yaml`). + +## Verifikation nach Änderungen + +Im `app/`-Verzeichnis: +``` +flutter analyze +flutter test +``` diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/app/.metadata b/app/.metadata new file mode 100644 index 0000000..d312f63 --- /dev/null +++ b/app/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "058e0af2c2b57e369d905a03ac9748b0ebf543c6" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 058e0af2c2b57e369d905a03ac9748b0ebf543c6 + base_revision: 058e0af2c2b57e369d905a03ac9748b0ebf543c6 + - platform: android + create_revision: 058e0af2c2b57e369d905a03ac9748b0ebf543c6 + base_revision: 058e0af2c2b57e369d905a03ac9748b0ebf543c6 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..b4885b7 --- /dev/null +++ b/app/README.md @@ -0,0 +1,17 @@ +# dmc_app + +Drone Mission Control - Missionsplanung und Flugsteuerung f\u00fcr Fixed-Wing-Drohnen + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/app/analysis_options.yaml b/app/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/app/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/app/android/.gitignore b/app/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/app/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/app/android/app/build.gradle.kts b/app/android/app/build.gradle.kts new file mode 100644 index 0000000..89f0886 --- /dev/null +++ b/app/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.dmc.dmc_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.dmc.dmc_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..74403a7 --- /dev/null +++ b/app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/android/app/src/main/kotlin/com/dmc/dmc_app/MainActivity.kt b/app/android/app/src/main/kotlin/com/dmc/dmc_app/MainActivity.kt new file mode 100644 index 0000000..e0a086d --- /dev/null +++ b/app/android/app/src/main/kotlin/com/dmc/dmc_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.dmc.dmc_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/app/android/app/src/main/res/drawable-v21/launch_background.xml b/app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/android/app/src/main/res/drawable/launch_background.xml b/app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..fc4b0ae6225c54ce8d106205785c5494b8b27265 GIT binary patch literal 4426 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 QFaQ7m07*qoM6N<$g7XcwQvd(} literal 0 HcmV?d00001 diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f2230fdf5bcb6dc85e00ecaad925f524cd1f908f GIT binary patch literal 2763 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 literal 0 HcmV?d00001 diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8045f1c3a4ad464c3a2765c59cc8ab20abf09976 GIT binary patch literal 11693 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 + + + + + + diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/android/build.gradle.kts b/app/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/app/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/app/android/gradle.properties b/app/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/app/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/app/android/settings.gradle.kts b/app/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/app/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/app/assets/icon/icon.png b/app/assets/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27a3fafa2662ba6829cd5dd66b172370e7a78bd0 GIT binary patch literal 25425 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 LTe Connect -> Upload -> Fly +/// (Architektur-Doku 2.2). +enum AppMode { plan, connect, upload, fly } + +class AppModeState { + const AppModeState({ + this.mode = AppMode.plan, + this.missionUploaded = false, + this.missionVerified = false, + this.droneArmed = false, + }); + + final AppMode mode; + final bool missionUploaded; + final bool missionVerified; + final bool droneArmed; + + /// Ready-to-Fly-Gate (Doku 2.2 / 4.5): Upload + verifiziert + disarmed. + /// Muss technisch erzwungen werden, nicht nur als UI-Konvention gelten. + bool get readyToFly => missionUploaded && missionVerified && !droneArmed; + + AppModeState copyWith({ + AppMode? mode, + bool? missionUploaded, + bool? missionVerified, + bool? droneArmed, + }) { + return AppModeState( + mode: mode ?? this.mode, + missionUploaded: missionUploaded ?? this.missionUploaded, + missionVerified: missionVerified ?? this.missionVerified, + droneArmed: droneArmed ?? this.droneArmed, + ); + } +} + +class AppModeCubit extends Cubit { + AppModeCubit() : super(const AppModeState()); + + void toConnect() => emit(state.copyWith(mode: AppMode.connect)); + + void toUpload() => emit(state.copyWith(mode: AppMode.upload)); + + void missionUploadConfirmed() => + emit(state.copyWith(missionUploaded: true)); + + void missionVerifiedConfirmed() => + emit(state.copyWith(missionVerified: true)); + + void droneArmedChanged(bool armed) => + emit(state.copyWith(droneArmed: armed)); + + /// Wirft, wenn das Ready-to-Fly-Gate nicht erfuellt ist (Doku 4.5). + void toFly() { + if (!state.readyToFly) { + throw StateError('Ready-to-Fly-Gate nicht erfuellt.'); + } + emit(state.copyWith(mode: AppMode.fly)); + } + + /// Verlassen des Fly-Modus setzt den kompletten Handover-Status zurueck. + void toPlan() => emit(const AppModeState()); +} diff --git a/app/lib/domain/mission/mission.dart b/app/lib/domain/mission/mission.dart new file mode 100644 index 0000000..97ff75d --- /dev/null +++ b/app/lib/domain/mission/mission.dart @@ -0,0 +1,24 @@ +import '../waypoint/flat_waypoint_list.dart'; + +/// Strategy-Pattern fuer erweiterbare Missionstypen (Architektur-Doku 3.2). +abstract class Mission { + String get name; + + FlatWaypointList toFlatWaypointList(); +} + +/// Wegpunkt-Streckenmission - aktuell einziger implementierter Missionstyp. +class TrackMission implements Mission { + const TrackMission({required this.name, required this.waypoints}); + + @override + final String name; + + final List waypoints; + + @override + FlatWaypointList toFlatWaypointList() => FlatWaypointList(waypoints); +} + +// SurveyAreaMission (Flaechenabflug) und SearchAreaMission (Suchmuster) sind +// im Strategy-Pattern vorgesehen, aber noch nicht spezifiziert (Doku 6). diff --git a/app/lib/domain/waypoint/flat_waypoint_list.dart b/app/lib/domain/waypoint/flat_waypoint_list.dart new file mode 100644 index 0000000..19efd06 --- /dev/null +++ b/app/lib/domain/waypoint/flat_waypoint_list.dart @@ -0,0 +1,61 @@ +/// Protokollneutrales Wegpunkt-Modell (Architektur-Doku 3.2). +/// Divergenz zu MSP/MAVLink findet ausschliesslich im Encoder-Schritt statt +/// (MspWaypointEncoder / MavlinkMissionEncoder), nicht in diesem Modell. +library; + +enum WaypointAction { none, loiter, landing, jump } + +class Waypoint { + const Waypoint({ + required this.lat, + required this.lon, + required this.altitudeM, + required this.speedMs, + required this.catchRadiusM, + this.action = WaypointAction.none, + this.jumpTargetIndex, + this.jumpRepeatCount, + }); + + final double lat; + final double lon; + final double altitudeM; + final double speedMs; + final double catchRadiusM; + final WaypointAction action; + + /// Nur bei action == jump gesetzt (iNAV Action-Code 6, Doku 7.1). + final int? jumpTargetIndex; + + /// Nur bei action == jump gesetzt; null bedeutet Endlos-Wiederholung. + final int? jumpRepeatCount; + + Waypoint copyWith({ + double? lat, + double? lon, + double? altitudeM, + double? speedMs, + double? catchRadiusM, + WaypointAction? action, + int? jumpTargetIndex, + int? jumpRepeatCount, + }) { + return Waypoint( + lat: lat ?? this.lat, + lon: lon ?? this.lon, + altitudeM: altitudeM ?? this.altitudeM, + speedMs: speedMs ?? this.speedMs, + catchRadiusM: catchRadiusM ?? this.catchRadiusM, + action: action ?? this.action, + jumpTargetIndex: jumpTargetIndex ?? this.jumpTargetIndex, + jumpRepeatCount: jumpRepeatCount ?? this.jumpRepeatCount, + ); + } +} + +/// Gemeinsame, protokollunabhaengige Datenstruktur aller Missionstypen. +class FlatWaypointList { + const FlatWaypointList(this.waypoints); + + final List waypoints; +} diff --git a/app/lib/main.dart b/app/lib/main.dart new file mode 100644 index 0000000..fd1dc32 --- /dev/null +++ b/app/lib/main.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import 'app_mode/app_mode_cubit.dart'; +import 'ui/app_shell.dart'; +import 'ui/dmc_colors.dart'; +import 'ui/screens/library/library_screen.dart'; +import 'ui/screens/settings/settings_screen.dart'; + +void main() { + runApp(const DmcApp()); +} + +final _router = GoRouter( + routes: [ + GoRoute(path: '/', builder: (context, state) => const AppShell()), + GoRoute( + path: '/settings', + builder: (context, state) => const SettingsScreen(), + ), + GoRoute( + path: '/library', + builder: (context, state) => const LibraryScreen(), + ), + ], +); + +class DmcApp extends StatelessWidget { + const DmcApp({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + child: BlocProvider( + create: (_) => AppModeCubit(), + child: MaterialApp.router( + title: 'Drone Mission Control', + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: const Color(0xFF0F0F0F), + colorScheme: ColorScheme.fromSeed( + seedColor: DmcColors.setBlue, + brightness: Brightness.dark, + ), + ), + routerConfig: _router, + ), + ), + ); + } +} diff --git a/app/lib/services/database/app_database.dart b/app/lib/services/database/app_database.dart new file mode 100644 index 0000000..96874ce --- /dev/null +++ b/app/lib/services/database/app_database.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +part 'app_database.g.dart'; + +/// Persistenz fuer Missionen (Architektur-Doku 4.15): Drift statt Hive, weil +/// indexierte Abfragen (Suche nach Name/Ort/Datum) benoetigt werden und das +/// Schema kompatibel zu einem kuenftigen lokal-first Server-Sync-Muster +/// (Dirty-Flags, Server-IDs) bleiben soll. +class Missions extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + TextColumn get location => text().nullable()(); + DateTimeColumn get createdAt => + dateTime().withDefault(currentDateAndTime)(); + TextColumn get waypointsJson => text()(); + BoolColumn get isDirty => boolean().withDefault(const Constant(true))(); + TextColumn get serverId => text().nullable()(); +} + +@DriftDatabase(tables: [Missions]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; + + static LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'dmc_missions.sqlite')); + return NativeDatabase.createInBackground(file); + }); + } +} diff --git a/app/lib/services/database/app_database.g.dart b/app/lib/services/database/app_database.g.dart new file mode 100644 index 0000000..fb01165 --- /dev/null +++ b/app/lib/services/database/app_database.g.dart @@ -0,0 +1,708 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class $MissionsTable extends Missions with TableInfo<$MissionsTable, Mission> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $MissionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _locationMeta = const VerificationMeta( + 'location', + ); + @override + late final GeneratedColumn location = GeneratedColumn( + 'location', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _waypointsJsonMeta = const VerificationMeta( + 'waypointsJson', + ); + @override + late final GeneratedColumn waypointsJson = GeneratedColumn( + 'waypoints_json', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _isDirtyMeta = const VerificationMeta( + 'isDirty', + ); + @override + late final GeneratedColumn isDirty = GeneratedColumn( + 'is_dirty', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_dirty" IN (0, 1))', + ), + defaultValue: const Constant(true), + ); + static const VerificationMeta _serverIdMeta = const VerificationMeta( + 'serverId', + ); + @override + late final GeneratedColumn serverId = GeneratedColumn( + 'server_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + location, + createdAt, + waypointsJson, + isDirty, + serverId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'missions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('location')) { + context.handle( + _locationMeta, + location.isAcceptableOrUnknown(data['location']!, _locationMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('waypoints_json')) { + context.handle( + _waypointsJsonMeta, + waypointsJson.isAcceptableOrUnknown( + data['waypoints_json']!, + _waypointsJsonMeta, + ), + ); + } else if (isInserting) { + context.missing(_waypointsJsonMeta); + } + if (data.containsKey('is_dirty')) { + context.handle( + _isDirtyMeta, + isDirty.isAcceptableOrUnknown(data['is_dirty']!, _isDirtyMeta), + ); + } + if (data.containsKey('server_id')) { + context.handle( + _serverIdMeta, + serverId.isAcceptableOrUnknown(data['server_id']!, _serverIdMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Mission map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Mission( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + location: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}location'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + waypointsJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}waypoints_json'], + )!, + isDirty: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_dirty'], + )!, + serverId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}server_id'], + ), + ); + } + + @override + $MissionsTable createAlias(String alias) { + return $MissionsTable(attachedDatabase, alias); + } +} + +class Mission extends DataClass implements Insertable { + final int id; + final String name; + final String? location; + final DateTime createdAt; + final String waypointsJson; + final bool isDirty; + final String? serverId; + const Mission({ + required this.id, + required this.name, + this.location, + required this.createdAt, + required this.waypointsJson, + required this.isDirty, + this.serverId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || location != null) { + map['location'] = Variable(location); + } + map['created_at'] = Variable(createdAt); + map['waypoints_json'] = Variable(waypointsJson); + map['is_dirty'] = Variable(isDirty); + if (!nullToAbsent || serverId != null) { + map['server_id'] = Variable(serverId); + } + return map; + } + + MissionsCompanion toCompanion(bool nullToAbsent) { + return MissionsCompanion( + id: Value(id), + name: Value(name), + location: location == null && nullToAbsent + ? const Value.absent() + : Value(location), + createdAt: Value(createdAt), + waypointsJson: Value(waypointsJson), + isDirty: Value(isDirty), + serverId: serverId == null && nullToAbsent + ? const Value.absent() + : Value(serverId), + ); + } + + factory Mission.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Mission( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + location: serializer.fromJson(json['location']), + createdAt: serializer.fromJson(json['createdAt']), + waypointsJson: serializer.fromJson(json['waypointsJson']), + isDirty: serializer.fromJson(json['isDirty']), + serverId: serializer.fromJson(json['serverId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'location': serializer.toJson(location), + 'createdAt': serializer.toJson(createdAt), + 'waypointsJson': serializer.toJson(waypointsJson), + 'isDirty': serializer.toJson(isDirty), + 'serverId': serializer.toJson(serverId), + }; + } + + Mission copyWith({ + int? id, + String? name, + Value location = const Value.absent(), + DateTime? createdAt, + String? waypointsJson, + bool? isDirty, + Value serverId = const Value.absent(), + }) => Mission( + id: id ?? this.id, + name: name ?? this.name, + location: location.present ? location.value : this.location, + createdAt: createdAt ?? this.createdAt, + waypointsJson: waypointsJson ?? this.waypointsJson, + isDirty: isDirty ?? this.isDirty, + serverId: serverId.present ? serverId.value : this.serverId, + ); + Mission copyWithCompanion(MissionsCompanion data) { + return Mission( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + location: data.location.present ? data.location.value : this.location, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + waypointsJson: data.waypointsJson.present + ? data.waypointsJson.value + : this.waypointsJson, + isDirty: data.isDirty.present ? data.isDirty.value : this.isDirty, + serverId: data.serverId.present ? data.serverId.value : this.serverId, + ); + } + + @override + String toString() { + return (StringBuffer('Mission(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('location: $location, ') + ..write('createdAt: $createdAt, ') + ..write('waypointsJson: $waypointsJson, ') + ..write('isDirty: $isDirty, ') + ..write('serverId: $serverId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + location, + createdAt, + waypointsJson, + isDirty, + serverId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Mission && + other.id == this.id && + other.name == this.name && + other.location == this.location && + other.createdAt == this.createdAt && + other.waypointsJson == this.waypointsJson && + other.isDirty == this.isDirty && + other.serverId == this.serverId); +} + +class MissionsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value location; + final Value createdAt; + final Value waypointsJson; + final Value isDirty; + final Value serverId; + const MissionsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.location = const Value.absent(), + this.createdAt = const Value.absent(), + this.waypointsJson = const Value.absent(), + this.isDirty = const Value.absent(), + this.serverId = const Value.absent(), + }); + MissionsCompanion.insert({ + this.id = const Value.absent(), + required String name, + this.location = const Value.absent(), + this.createdAt = const Value.absent(), + required String waypointsJson, + this.isDirty = const Value.absent(), + this.serverId = const Value.absent(), + }) : name = Value(name), + waypointsJson = Value(waypointsJson); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? location, + Expression? createdAt, + Expression? waypointsJson, + Expression? isDirty, + Expression? serverId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (location != null) 'location': location, + if (createdAt != null) 'created_at': createdAt, + if (waypointsJson != null) 'waypoints_json': waypointsJson, + if (isDirty != null) 'is_dirty': isDirty, + if (serverId != null) 'server_id': serverId, + }); + } + + MissionsCompanion copyWith({ + Value? id, + Value? name, + Value? location, + Value? createdAt, + Value? waypointsJson, + Value? isDirty, + Value? serverId, + }) { + return MissionsCompanion( + id: id ?? this.id, + name: name ?? this.name, + location: location ?? this.location, + createdAt: createdAt ?? this.createdAt, + waypointsJson: waypointsJson ?? this.waypointsJson, + isDirty: isDirty ?? this.isDirty, + serverId: serverId ?? this.serverId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (location.present) { + map['location'] = Variable(location.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (waypointsJson.present) { + map['waypoints_json'] = Variable(waypointsJson.value); + } + if (isDirty.present) { + map['is_dirty'] = Variable(isDirty.value); + } + if (serverId.present) { + map['server_id'] = Variable(serverId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MissionsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('location: $location, ') + ..write('createdAt: $createdAt, ') + ..write('waypointsJson: $waypointsJson, ') + ..write('isDirty: $isDirty, ') + ..write('serverId: $serverId') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $MissionsTable missions = $MissionsTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [missions]; +} + +typedef $$MissionsTableCreateCompanionBuilder = + MissionsCompanion Function({ + Value id, + required String name, + Value location, + Value createdAt, + required String waypointsJson, + Value isDirty, + Value serverId, + }); +typedef $$MissionsTableUpdateCompanionBuilder = + MissionsCompanion Function({ + Value id, + Value name, + Value location, + Value createdAt, + Value waypointsJson, + Value isDirty, + Value serverId, + }); + +class $$MissionsTableFilterComposer + extends Composer<_$AppDatabase, $MissionsTable> { + $$MissionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get location => $composableBuilder( + column: $table.location, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get waypointsJson => $composableBuilder( + column: $table.waypointsJson, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get isDirty => $composableBuilder( + column: $table.isDirty, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get serverId => $composableBuilder( + column: $table.serverId, + builder: (column) => ColumnFilters(column), + ); +} + +class $$MissionsTableOrderingComposer + extends Composer<_$AppDatabase, $MissionsTable> { + $$MissionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get location => $composableBuilder( + column: $table.location, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get waypointsJson => $composableBuilder( + column: $table.waypointsJson, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get isDirty => $composableBuilder( + column: $table.isDirty, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get serverId => $composableBuilder( + column: $table.serverId, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$MissionsTableAnnotationComposer + extends Composer<_$AppDatabase, $MissionsTable> { + $$MissionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get location => + $composableBuilder(column: $table.location, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get waypointsJson => $composableBuilder( + column: $table.waypointsJson, + builder: (column) => column, + ); + + GeneratedColumn get isDirty => + $composableBuilder(column: $table.isDirty, builder: (column) => column); + + GeneratedColumn get serverId => + $composableBuilder(column: $table.serverId, builder: (column) => column); +} + +class $$MissionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $MissionsTable, + Mission, + $$MissionsTableFilterComposer, + $$MissionsTableOrderingComposer, + $$MissionsTableAnnotationComposer, + $$MissionsTableCreateCompanionBuilder, + $$MissionsTableUpdateCompanionBuilder, + (Mission, BaseReferences<_$AppDatabase, $MissionsTable, Mission>), + Mission, + PrefetchHooks Function() + > { + $$MissionsTableTableManager(_$AppDatabase db, $MissionsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$MissionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$MissionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$MissionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value location = const Value.absent(), + Value createdAt = const Value.absent(), + Value waypointsJson = const Value.absent(), + Value isDirty = const Value.absent(), + Value serverId = const Value.absent(), + }) => MissionsCompanion( + id: id, + name: name, + location: location, + createdAt: createdAt, + waypointsJson: waypointsJson, + isDirty: isDirty, + serverId: serverId, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String name, + Value location = const Value.absent(), + Value createdAt = const Value.absent(), + required String waypointsJson, + Value isDirty = const Value.absent(), + Value serverId = const Value.absent(), + }) => MissionsCompanion.insert( + id: id, + name: name, + location: location, + createdAt: createdAt, + waypointsJson: waypointsJson, + isDirty: isDirty, + serverId: serverId, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$MissionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $MissionsTable, + Mission, + $$MissionsTableFilterComposer, + $$MissionsTableOrderingComposer, + $$MissionsTableAnnotationComposer, + $$MissionsTableCreateCompanionBuilder, + $$MissionsTableUpdateCompanionBuilder, + (Mission, BaseReferences<_$AppDatabase, $MissionsTable, Mission>), + Mission, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$MissionsTableTableManager get missions => + $$MissionsTableTableManager(_db, _db.missions); +} diff --git a/app/lib/services/mission_sync_service.dart b/app/lib/services/mission_sync_service.dart new file mode 100644 index 0000000..b4b0cc0 --- /dev/null +++ b/app/lib/services/mission_sync_service.dart @@ -0,0 +1,26 @@ +import '../app_mode/app_mode_cubit.dart'; +import '../domain/waypoint/flat_waypoint_list.dart'; +import '../transport/flight_controller_link.dart'; + +/// Orchestriert Connect -> Verify -> Upload -> Ready-to-Fly und erzwingt das +/// Ready-to-Fly-Gate (Architektur-Doku 3.3 / 4.5). +class MissionSyncService { + MissionSyncService(this._link, this._appMode); + + final FlightControllerLink _link; + final AppModeCubit _appMode; + + Future syncAndUpload(FlatWaypointList mission) async { + await _link.connect(); + // uploadMission() der jeweiligen FlightControllerLink-Implementierung + // verweigert den Upload selbst, falls die Drohne armed ist (Doku 4.5) - + // dieser Service verlaesst sich bewusst nicht zusaetzlich auf einen + // eigenen Telemetrie-Vorab-Check, um keine zwei Wahrheitsquellen zu haben. + await _link.uploadMission(mission); + _appMode.missionUploadConfirmed(); + + // TODO: echte Verifikation gegen FC-Rueckmeldung, sobald ein + // Protokoll-Adapter (MSP/MAVLink) eine Upload-Bestaetigung liefert. + _appMode.missionVerifiedConfirmed(); + } +} diff --git a/app/lib/services/telemetry_service.dart b/app/lib/services/telemetry_service.dart new file mode 100644 index 0000000..b23a6bf --- /dev/null +++ b/app/lib/services/telemetry_service.dart @@ -0,0 +1,15 @@ +import '../transport/flight_controller_link.dart'; + +/// Abonniert Live-Telemetrie vom FC und reicht normalisierte Frames an die +/// UI weiter (Architektur-Doku 3.4). +/// +/// Laeuft im MVP noch synchron auf dem UI-Isolate. Die Auslagerung in ein +/// eigenes Dart-Isolate (Doku 4.21) ist erst sinnvoll zu verifizieren, sobald +/// ein echter Protokoll-Adapter reale Telemetrie-Raten liefert. +class TelemetryService { + TelemetryService(this._link); + + final FlightControllerLink _link; + + Stream get frames => _link.subscribeTelemetry(); +} diff --git a/app/lib/transport/flight_controller_link.dart b/app/lib/transport/flight_controller_link.dart new file mode 100644 index 0000000..d121bdc --- /dev/null +++ b/app/lib/transport/flight_controller_link.dart @@ -0,0 +1,57 @@ +import '../domain/waypoint/flat_waypoint_list.dart'; + +/// Abstrakte Flugmodi, protokollunabhaengig (Architektur-Doku 3.1). +enum FlightMode { missionRun, guidedPoint, returnHome, hold } + +/// Feature-Flags des angebundenen Flightcontrollers (Doku 3.1). +class FcCapabilities { + const FcCapabilities({ + required this.supportsMultiMission, + required this.supportsInFlightUpload, + required this.maxWaypoints, + }); + + final bool supportsMultiMission; + final bool supportsInFlightUpload; + final int maxWaypoints; +} + +/// Normalisiertes Telemetrie-Frame, das an die UI weitergereicht wird (Doku 3.4). +class TelemetryFrame { + const TelemetryFrame({ + required this.lat, + required this.lon, + required this.altitudeM, + required this.speedMs, + required this.armed, + this.activeWaypointIndex, + }); + + final double lat; + final double lon; + final double altitudeM; + final double speedMs; + final bool armed; + final int? activeWaypointIndex; +} + +/// Zentrale Abstraktion fuer alle Flightcontroller-Interaktionen (Doku 3.1). +/// Implementierungen: MspFlightControllerLink (iNAV), MavlinkFlightControllerLink +/// (ArduPilot, Post-MVP), MockFlightControllerLink (Tests ohne Hardware). +abstract class FlightControllerLink { + FcCapabilities get capabilities; + + Future connect(); + + Future disconnect(); + + Future uploadMission(FlatWaypointList mission); + + Future setFlightMode(FlightMode mode); + + Future arm(); + + Stream subscribeTelemetry(); + + Future readActiveWaypointIndex(); +} diff --git a/app/lib/transport/mock/mock_flight_controller_link.dart b/app/lib/transport/mock/mock_flight_controller_link.dart new file mode 100644 index 0000000..4c66281 --- /dev/null +++ b/app/lib/transport/mock/mock_flight_controller_link.dart @@ -0,0 +1,71 @@ +import 'dart:async'; + +import '../../domain/waypoint/flat_waypoint_list.dart'; +import '../flight_controller_link.dart'; + +/// Synthetische Telemetrie fuer UI-Entwicklung/Tests ohne Hardware oder SITL +/// (Architektur-Doku 3.1). +class MockFlightControllerLink implements FlightControllerLink { + bool _armed = false; + int? _activeWaypointIndex; + StreamController? _telemetryController; + Timer? _ticker; + + @override + FcCapabilities get capabilities => const FcCapabilities( + supportsMultiMission: true, + supportsInFlightUpload: false, + maxWaypoints: 60, + ); + + @override + Future connect() async { + _telemetryController = StreamController.broadcast(); + _ticker = Timer.periodic(const Duration(seconds: 1), (_) { + _telemetryController?.add(TelemetryFrame( + lat: 52.5, + lon: 13.4, + altitudeM: 120, + speedMs: 18, + armed: _armed, + activeWaypointIndex: _activeWaypointIndex, + )); + }); + } + + @override + Future disconnect() async { + _ticker?.cancel(); + await _telemetryController?.close(); + _telemetryController = null; + } + + @override + Future uploadMission(FlatWaypointList mission) async { + // iNAV/MAVLink erlauben Mission-Upload nur im unarmed-Zustand (Doku 4.5). + if (_armed) { + throw StateError('Mission-Upload verweigert: Drohne ist armed.'); + } + _activeWaypointIndex = mission.waypoints.isEmpty ? null : 0; + } + + @override + Future setFlightMode(FlightMode mode) async {} + + @override + Future arm() async { + _armed = true; + } + + @override + Stream subscribeTelemetry() { + final controller = _telemetryController; + if (controller == null) { + throw StateError('subscribeTelemetry() vor connect() aufgerufen.'); + } + return controller.stream; + } + + @override + Future readActiveWaypointIndex() async => _activeWaypointIndex; +} diff --git a/app/lib/transport/msp/msp_flight_controller_link.dart b/app/lib/transport/msp/msp_flight_controller_link.dart new file mode 100644 index 0000000..0b3e2c7 --- /dev/null +++ b/app/lib/transport/msp/msp_flight_controller_link.dart @@ -0,0 +1,39 @@ +import '../../domain/waypoint/flat_waypoint_list.dart'; +import '../flight_controller_link.dart'; + +/// iNAV-Anbindung via MSP (Architektur-Doku 3.1). Eigenstaendiger Adapter, +/// da kein fertiges Dart-Paket fuer MSP verfuegbar ist (Doku 4.19). +/// +/// Protokoll-Implementierung (Framing, MSP_SET_WP, MSP_NAV_STATUS, ...) ist +/// noch offen - dies ist bewusst nur das Geruest fuer die Abstraktion. +class MspFlightControllerLink implements FlightControllerLink { + MspFlightControllerLink({required this.host, required this.port}); + + final String host; + final int port; + + @override + FcCapabilities get capabilities => throw UnimplementedError(); + + @override + Future connect() => throw UnimplementedError(); + + @override + Future disconnect() => throw UnimplementedError(); + + @override + Future uploadMission(FlatWaypointList mission) => + throw UnimplementedError(); + + @override + Future setFlightMode(FlightMode mode) => throw UnimplementedError(); + + @override + Future arm() => throw UnimplementedError(); + + @override + Stream subscribeTelemetry() => throw UnimplementedError(); + + @override + Future readActiveWaypointIndex() => throw UnimplementedError(); +} diff --git a/app/lib/ui/app_shell.dart b/app/lib/ui/app_shell.dart new file mode 100644 index 0000000..2c7f940 --- /dev/null +++ b/app/lib/ui/app_shell.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../app_mode/app_mode_cubit.dart'; +import 'screens/fly/fly_screen.dart'; +import 'screens/plan/plan_screen.dart'; + +/// Schaltet zwischen Plan- und Fly-Screen um, gesteuert von der +/// App-Mode State Machine (Architektur-Doku 2.2). Connect/Upload sind +/// Uebergangszustaende innerhalb des Plan-Screens (Handover-Prozess). +class AppShell extends StatelessWidget { + const AppShell({super.key}); + + @override + Widget build(BuildContext context) { + final mode = context.select((AppModeCubit c) => c.state.mode); + return mode == AppMode.fly ? const FlyScreen() : const PlanScreen(); + } +} diff --git a/app/lib/ui/dmc_colors.dart b/app/lib/ui/dmc_colors.dart new file mode 100644 index 0000000..988a836 --- /dev/null +++ b/app/lib/ui/dmc_colors.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +/// Markenfarben, uebernommen aus dem HTML-Demonstrator +/// ("Drone Mission Control.html"), damit Flutter-UI und Prototyp konsistent +/// bleiben. +abstract final class DmcColors { + static const setBlue = Color(0xFF4D9DFF); + static const setBlueLight = Color(0xFF8FD0FF); + static const liveGreen = Color(0xFF3ECF8E); + static const warnRed = Color(0xFFE2574A); + static const pafAmber = Color(0xFFF0C86E); + static const editModeGreen = Color(0xFF50C878); + static const flyModeRed = Color(0xFFE2574A); +} diff --git a/app/lib/ui/screens/fly/fly_screen.dart b/app/lib/ui/screens/fly/fly_screen.dart new file mode 100644 index 0000000..bbeb62a --- /dev/null +++ b/app/lib/ui/screens/fly/fly_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +/// Fly-Modus: Telemetrie, Draeder, Point-and-Fly-Fadenkreuz +/// (Architektur-Doku 3.10/3.11). Platzhalter bis Telemetry-Service und +/// FlightControllerLink verdrahtet sind. +class FlyScreen extends StatelessWidget { + const FlyScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center(child: Text('Fly')), + ); + } +} diff --git a/app/lib/ui/screens/library/library_screen.dart b/app/lib/ui/screens/library/library_screen.dart new file mode 100644 index 0000000..087ad40 --- /dev/null +++ b/app/lib/ui/screens/library/library_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +/// Missionsuebersicht (Architektur-Doku 3.5/7.5): Suche nach Name/Ort/Datum +/// ueber Drift, sobald AppDatabase verdrahtet ist. +class LibraryScreen extends StatelessWidget { + const LibraryScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Missions')), + body: const Center(child: Text('Library')), + ); + } +} diff --git a/app/lib/ui/screens/plan/plan_screen.dart b/app/lib/ui/screens/plan/plan_screen.dart new file mode 100644 index 0000000..fd98865 --- /dev/null +++ b/app/lib/ui/screens/plan/plan_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.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. +class PlanScreen extends StatelessWidget { + const PlanScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center(child: Text('Plan')), + ); + } +} diff --git a/app/lib/ui/screens/settings/settings_screen.dart b/app/lib/ui/screens/settings/settings_screen.dart new file mode 100644 index 0000000..e7716ff --- /dev/null +++ b/app/lib/ui/screens/settings/settings_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +/// Drohnen-Profile: FC-Typ, Flugleistungsdaten, Standardprofil T1 Ranger +/// (Architektur-Doku 3.5). +class SettingsScreen extends StatelessWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Drone Profiles')), + body: const Center(child: Text('Settings')), + ); + } +} diff --git a/app/pubspec.lock b/app/pubspec.lock new file mode 100644 index 0000000..bbe2346 --- /dev/null +++ b/app/pubspec.lock @@ -0,0 +1,1050 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + url: "https://pub.dev" + source: hosted + version: "99.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + bloc: + dependency: transitive + description: + name: bloc + sha256: e03b235924e4f509c27b5d6b2f949200e0a91149a9818b4f65eeb56662b75413 + url: "https://pub.dev" + source: hosted + version: "9.2.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + build_config: + dependency: transitive + description: + name: build_config + sha256: f2c223156a26eea323e6244b85141d76413a80aeee9fe0b380773789fabaf8ae + url: "https://pub.dev" + source: hosted + version: "1.3.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + url: "https://pub.dev" + source: hosted + version: "4.1.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + dart_earcut: + dependency: transitive + description: + name: dart_earcut + sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b + url: "https://pub.dev" + source: hosted + version: "1.2.0" + dart_polylabel2: + dependency: transitive + description: + name: dart_polylabel2 + sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 + url: "https://pub.dev" + source: hosted + version: "3.1.8" + drift: + dependency: "direct main" + description: + name: drift + sha256: "84688491040b0ceb26575709a84d701c84ad4df4d8aff020adf6d85f155fb0dd" + url: "https://pub.dev" + source: hosted + version: "2.34.2" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "9cfff1576b49725da0d32c040651a41ae195e8c4af8d8da301593e41d7abc2f7" + url: "https://pub.dev" + source: hosted + version: "2.34.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + url: "https://pub.dev" + source: hosted + version: "23.5.26" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: ce1debbb29cade61964334b2a19c7c76e5bb2ef7dacf126c6424ee44036232f8 + url: "https://pub.dev" + source: hosted + version: "8.3.1" + flutter_map_tile_caching: + dependency: "direct main" + description: + name: flutter_map_tile_caching + sha256: "90e097223d8ab74425cf15b449a03adfa4d4c28406dc757e1c396aff0f9beba7" + url: "https://pub.dev" + source: hosted + version: "10.1.1" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: a441fd079f11969f526ec04556ba9819fee7d981bdf50fab858066807b36cff9 + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a" + url: "https://pub.dev" + source: hosted + version: "17.3.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + url: "https://pub.dev" + source: hosted + version: "4.9.1" + intl: + dependency: transitive + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.dev" + source: hosted + version: "0.9.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + listen: + dependency: transitive + description: + name: listen + sha256: cb0ad74d7453d8f83a7c1fae401828681ef5df21d41a3a26a04af590c985d085 + url: "https://pub.dev" + source: hosted + version: "1.0.0-beta.4" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: "385e7168ecc77eb545220223c49eef8ab249da7bf57f22781c40a04d23fb196f" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f9c168717100ae6d9fee9ffb0be379bf1f8b26b0f6bcbd4fdddcd931993a6a72 + url: "https://pub.dev" + source: hosted + version: "0.19.2" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objectbox: + dependency: transitive + description: + name: objectbox + sha256: "3cc186749178a3556e1020c9082d0897d0f9ecbdefcc27320e65c5bc650f0e57" + url: "https://pub.dev" + source: hosted + version: "4.3.1" + objectbox_flutter_libs: + dependency: transitive + description: + name: objectbox_flutter_libs + sha256: cd754766e04229a4f51250f121813d9a3c1a74fc21cd68e48b3c6085cbcd6c85 + url: "https://pub.dev" + source: hosted + version: "4.3.1" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: ddcedc1f7876e62717de43ab3491e2829bdad0b028261805f94aa080967e5859 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: ccd1d45f07d20f894a975eeaa64d14a0dbbcfd0825bb59c3cd89837c2c21b436 + url: "https://pub.dev" + source: hosted + version: "3.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + simple_sparse_list: + dependency: transitive + description: + name: simple_sparse_list + sha256: aa648fd240fa39b49dcd11c19c266990006006de6699a412de485695910fbc1f + url: "https://pub.dev" + source: hosted + version: "0.1.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 + url: "https://pub.dev" + source: hosted + version: "4.2.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: c73fd75df1332d76a6257f4823ae4df9c791f522b97e4a60cbcad214de1becf4 + url: "https://pub.dev" + source: hosted + version: "3.5.0" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454" + url: "https://pub.dev" + source: hosted + version: "0.6.0+eol" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "40bdddb306a727be9ce510bd2d2b9a6c9db6c586d846ef7b22e3990a2b24f02d" + url: "https://pub.dev" + source: hosted + version: "0.44.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + url: "https://pub.dev" + source: hosted + version: "1.31.0" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + test_core: + dependency: transitive + description: + name: test_core + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + url: "https://pub.dev" + source: hosted + version: "0.6.17" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unicode: + dependency: transitive + description: + name: unicode + sha256: a6f7bcfc8ea1d5ce1f6c0b1c39117a9919f4953edd9fd7a64090a9796c499b57 + url: "https://pub.dev" + source: hosted + version: "1.1.9" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.2 <4.0.0" + flutter: ">=3.38.4" diff --git a/app/pubspec.yaml b/app/pubspec.yaml new file mode 100644 index 0000000..c9d895e --- /dev/null +++ b/app/pubspec.yaml @@ -0,0 +1,112 @@ +name: dmc_app +description: "Drone Mission Control - Missionsplanung und Flugsteuerung f\\u00fcr Fixed-Wing-Drohnen" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +flutter_launcher_icons: + android: true + ios: false + image_path: "assets/icon/icon.png" + min_sdk_android: 21 + +environment: + sdk: ^3.12.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + flutter_map: ^8.3.1 + latlong2: ^0.9.1 + flutter_map_tile_caching: ^10.1.1 + drift: ^2.34.2 + sqlite3_flutter_libs: ^0.6.0+eol + path_provider: ^2.1.6 + path: ^1.9.1 + http: ^1.6.0 + flutter_bloc: ^9.1.1 + flutter_riverpod: ^3.4.1 + go_router: ^17.3.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + drift_dev: ^2.34.0 + build_runner: ^2.15.1 + flutter_launcher_icons: ^0.14.4 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + assets: + - assets/icon/icon.png + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart new file mode 100644 index 0000000..ca17604 --- /dev/null +++ b/app/test/widget_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +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 { + await tester.pumpWidget( + ProviderScope( + child: BlocProvider( + create: (_) => AppModeCubit(), + child: const MaterialApp(home: AppShell()), + ), + ), + ); + + expect(find.text('Plan'), findsOneWidget); + }); +}