From c7e4e5bf6316d8c216f5525c263275a7b3eeeb11 Mon Sep 17 00:00:00 2001 From: Constantin Leue <77660791+Consti3D@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:11:28 +0200 Subject: [PATCH] fixed drone status menue grid view bug. fields are shown again --- .../widgets/drone_status_warnings_panel.dart | 79 +++++++++++++---- app/pubspec.lock | 2 +- app/pubspec.yaml | 1 + .../migration_v7_to_v8_repro_test.dart | 84 +++++++++++++++++++ 4 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 app/test/services/database/migration_v7_to_v8_repro_test.dart diff --git a/app/lib/ui/widgets/drone_status_warnings_panel.dart b/app/lib/ui/widgets/drone_status_warnings_panel.dart index dd5a76e..4494ad3 100644 --- a/app/lib/ui/widgets/drone_status_warnings_panel.dart +++ b/app/lib/ui/widgets/drone_status_warnings_panel.dart @@ -127,11 +127,12 @@ class _DroneStatusWarningsPanelState extends ConsumerState const Center( @@ -171,33 +172,81 @@ class _DroneStatusWarningsPanelState extends ConsumerState _statusGrid(List tiles) { + final rows = []; + for (var i = 0; i < tiles.length; i += 2) { + final hasPair = i + 1 < tiles.length; + // Kein CrossAxisAlignment.stretch: diese Row sitzt direkt als + // ListView-Kind, das seinen Kindern unbegrenzte Hoehe gibt (0<=h<= + // Infinity) - stretch verlangt aber eine endliche Hoehe, um die Kacheln + // darauf zu strecken, und wirft sonst "BoxConstraints forces an + // infinite height" (fuehrte zum komplett schwarzen Status-Tab). Default + // (center) reicht, da beide Kacheln einer Zeile ohnehin fast gleich + // hohen Inhalt haben. + rows.add(Row( + children: [ + Expanded(child: tiles[i]), + if (hasPair) ...[ + const SizedBox(width: 8), + Expanded(child: tiles[i + 1]), + ], + ], + )); + } + return rows; + } + static const _signalColors = { SignalLevel.green: DmcColors.flyModeColor, SignalLevel.yellow: DmcColors.pafAmber, SignalLevel.red: DmcColors.warnRed, }; + // Label ueber Wert statt nebeneinander (Doku: "2 felder pro zeile") - bei + // halber Zeilenbreite kollidiert ein nebeneinander angeordnetes Label wie + // "GPS precision (HDOP)" sonst mit dem Wert; gestapelt bleibt auch bei + // laengeren Labeln/Werten genug Platz. Widget _statusRow(String label, String value, {SignalLevel? level}) { final valueColor = level != null ? _signalColors[level]! : Colors.white; return Container( margin: const EdgeInsets.symmetric(vertical: 4), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.04), borderRadius: BorderRadius.circular(10), ), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(child: Text(label, style: const TextStyle(color: Colors.white70))), - if (level != null) ...[ - Container( - width: 8, - height: 8, - margin: const EdgeInsets.only(right: 8), - decoration: BoxDecoration(color: valueColor, shape: BoxShape.circle), - ), - ], - Text(value, style: TextStyle(color: valueColor, fontWeight: FontWeight.w600)), + Text( + label, + style: const TextStyle(color: Colors.white70, fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Row( + children: [ + if (level != null) ...[ + Container( + width: 8, + height: 8, + margin: const EdgeInsets.only(right: 6), + decoration: BoxDecoration(color: valueColor, shape: BoxShape.circle), + ), + ], + Flexible( + child: Text( + value, + style: TextStyle(color: valueColor, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ], ), ); diff --git a/app/pubspec.lock b/app/pubspec.lock index 46ee97c..505c321 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -878,7 +878,7 @@ packages: source: hosted version: "1.10.2" sqlite3: - dependency: transitive + dependency: "direct dev" description: name: sqlite3 sha256: c73fd75df1332d76a6257f4823ae4df9c791f522b97e4a60cbcad214de1becf4 diff --git a/app/pubspec.yaml b/app/pubspec.yaml index f0f3b51..faad00a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: wakelock_plus: ^1.7.0 dev_dependencies: + sqlite3: ^3.5.0 flutter_test: sdk: flutter diff --git a/app/test/services/database/migration_v7_to_v8_repro_test.dart b/app/test/services/database/migration_v7_to_v8_repro_test.dart new file mode 100644 index 0000000..89bac0f --- /dev/null +++ b/app/test/services/database/migration_v7_to_v8_repro_test.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sqlite3/sqlite3.dart' as sqlite3; +import 'package:dmc_app/services/database/app_database.dart'; +import 'package:dmc_app/services/database/drone_profile_repository.dart'; + +/// Reproduziert das Upgrade eines echten, bereits laufenden Geraets (Schema +/// v7, DroneProfiles ohne die neuen Ampel-Schwellwert-Spalten) auf das +/// aktuelle Schema (v8): baut die v7-Tabellenstruktur samt einer +/// Bestandszeile roh per sqlite3 auf (wie auf einem Geraet, das die App vor +/// diesem Update installiert hatte), oeffnet die Datei dann ganz normal ueber +/// AppDatabase und prueft, dass die Migration ohne Exception durchlaeuft und +/// die neuen Spalten mit den erwarteten Default-Werten befuellt werden. +void main() { + test('Upgrade von Schema v7 auf v8 befuellt neue Spalten mit Defaults', () async { + final dir = await Directory.systemTemp.createTemp('dmc_migration_test'); + final file = File('${dir.path}/test.sqlite'); + + final raw = sqlite3.sqlite3.open(file.path); + raw.execute(''' + CREATE TABLE missions ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + location TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + waypoints_json TEXT NOT NULL, + is_dirty INTEGER NOT NULL DEFAULT 1, + server_id TEXT, + terrain_profile_json TEXT + ); + CREATE TABLE drone_profiles ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + min_speed REAL NOT NULL, + cruise_speed REAL NOT NULL, + max_speed REAL NOT NULL, + min_turn_radius REAL NOT NULL, + loiter_radius REAL NOT NULL, + catch_radius REAL NOT NULL, + max_climb_rate REAL NOT NULL, + max_descent_rate REAL NOT NULL, + max_range_m REAL NOT NULL, + max_endurance_min REAL NOT NULL, + max_altitude_m REAL NOT NULL + ); + CREATE TABLE app_settings_table ( + id INTEGER NOT NULL PRIMARY KEY, + active_drone_profile_id INTEGER, + current_mission_id INTEGER, + connection_device_address TEXT, + connection_auto_connect INTEGER NOT NULL DEFAULT 1, + connection_type TEXT NOT NULL DEFAULT 'wifi', + connection_wifi_host TEXT, + connection_wifi_port INTEGER NOT NULL DEFAULT 14550, + connection_wifi_ssid_prefix TEXT NOT NULL DEFAULT 'mLRS-', + connection_wifi_last_ssid TEXT + ); + INSERT INTO drone_profiles ( + id, name, min_speed, cruise_speed, max_speed, min_turn_radius, + loiter_radius, catch_radius, max_climb_rate, max_descent_rate, + max_range_m, max_endurance_min, max_altitude_m + ) VALUES ( + 1, 'T1 Ranger', 13, 15, 25, 35, 100, 60, 5, 6, 15000, 45, 2000 + ); + PRAGMA user_version = 7; + '''); + raw.close(); + + final db = AppDatabase.forTesting(NativeDatabase(file)); + final repo = DroneProfileRepository(db); + + final all = await repo.loadAll(); + expect(all, hasLength(1)); + expect(all.first.name, 'T1 Ranger'); + expect(all.first.gpsSatGreenMin, 8.0); + expect(all.first.batteryVoltageRedMinV, 19.2); + + await db.close(); + await dir.delete(recursive: true); + }); +}