UI adjustments
This commit is contained in:
@@ -47,16 +47,16 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text('Verbindung', style: Theme.of(context).textTheme.titleMedium),
|
Text('Connection', style: Theme.of(context).textTheme.titleMedium),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_StatePill(state: linkState),
|
_StatePill(state: linkState),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Text('Art: Bluetooth'),
|
const Text('Type: Bluetooth'),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
const Text(
|
const Text(
|
||||||
'WLAN und USB folgen spaeter (Doku Kommunikationsschicht 6/7).',
|
'Wi-Fi and USB support are coming later.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
if (errorReason != null) ...[
|
if (errorReason != null) ...[
|
||||||
@@ -68,24 +68,24 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => _connect(ref),
|
onPressed: () => _connect(ref),
|
||||||
child: const Text('Verbinden'),
|
child: const Text('Connect'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => ref.read(bluetoothTransportProvider).disconnect(),
|
onPressed: () => ref.read(bluetoothTransportProvider).disconnect(),
|
||||||
child: const Text('Trennen'),
|
child: const Text('Disconnect'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: BluetoothSystemSettings.openBluetoothSettings,
|
onPressed: BluetoothSystemSettings.openBluetoothSettings,
|
||||||
child: const Text('Geraet koppeln'),
|
child: const Text('Pair device'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Beim Wechsel in Fly automatisch verbinden'),
|
title: const Text('Auto-connect when entering Fly mode'),
|
||||||
value: connectionSettings?.connectionAutoConnect ?? true,
|
value: connectionSettings?.connectionAutoConnect ?? true,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await ref
|
await ref
|
||||||
@@ -95,13 +95,13 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text('Gekoppelte Geraete', style: Theme.of(context).textTheme.titleSmall),
|
Text('Paired devices', style: Theme.of(context).textTheme.titleSmall),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
bondedDevices.when(
|
bondedDevices.when(
|
||||||
data: (devices) => devices.isEmpty
|
data: (devices) => devices.isEmpty
|
||||||
? const Text(
|
? const Text(
|
||||||
'Keine gekoppelten Geraete. Ueber "Geraet koppeln" '
|
'No paired devices yet. Pair one once via the system '
|
||||||
'zunaechst einmalig ueber den Systemdialog koppeln.',
|
'dialog using "Pair device".',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
)
|
)
|
||||||
: RadioGroup<String>(
|
: RadioGroup<String>(
|
||||||
@@ -129,13 +129,13 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
child: LinearProgressIndicator(),
|
child: LinearProgressIndicator(),
|
||||||
),
|
),
|
||||||
error: (e, _) => Text(
|
error: (e, _) => Text(
|
||||||
'Gekoppelte Geraete konnten nicht gelesen werden: $e',
|
'Could not read paired devices: $e',
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Verbindungsprotokoll ($packetCount Datenpakete empfangen)',
|
'Connection log ($packetCount packets received)',
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -143,7 +143,7 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
height: 120,
|
height: 120,
|
||||||
child: log.isEmpty
|
child: log.isEmpty
|
||||||
? const Text(
|
? const Text(
|
||||||
'Noch keine Ereignisse.',
|
'No events yet.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
@@ -203,10 +203,10 @@ class _StatePill extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (label, color) = switch (state) {
|
final (label, color) = switch (state) {
|
||||||
LinkState.connected => ('Verbunden', DmcColors.liveGreen),
|
LinkState.connected => ('Connected', DmcColors.liveGreen),
|
||||||
LinkState.connecting => ('Verbinde ...', DmcColors.pafAmber),
|
LinkState.connecting => ('Connecting…', DmcColors.pafAmber),
|
||||||
LinkState.error => ('Fehler', DmcColors.warnRed),
|
LinkState.error => ('Error', DmcColors.warnRed),
|
||||||
LinkState.disconnected || null => ('Getrennt', Colors.grey),
|
LinkState.disconnected || null => ('Disconnected', Colors.grey),
|
||||||
};
|
};
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
@@ -229,29 +229,28 @@ class _ErrorHint extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (text, action) = switch (reason) {
|
final (text, action) = switch (reason) {
|
||||||
LinkErrorReason.bluetoothOff => (
|
LinkErrorReason.bluetoothOff => (
|
||||||
'Bluetooth ist aus. Bitte in den Systemeinstellungen einschalten.',
|
'Bluetooth is off. Please enable it in system settings.',
|
||||||
_HintAction('Einstellungen', BluetoothSystemSettings.openBluetoothSettings),
|
_HintAction('Settings', BluetoothSystemSettings.openBluetoothSettings),
|
||||||
),
|
),
|
||||||
LinkErrorReason.permissionDenied => (
|
LinkErrorReason.permissionDenied => (
|
||||||
'"Geraete in der Naehe"-Berechtigung wird benoetigt. Bitte erneut '
|
'The "Nearby devices" permission is required. Please tap '
|
||||||
'auf "Verbinden" tippen.',
|
'"Connect" again.',
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
LinkErrorReason.permissionDeniedPermanently => (
|
LinkErrorReason.permissionDeniedPermanently => (
|
||||||
'"Geraete in der Naehe"-Berechtigung wurde dauerhaft abgelehnt. '
|
'The "Nearby devices" permission was permanently denied. Please '
|
||||||
'Bitte in den App-Einstellungen manuell erteilen.',
|
'grant it manually in the app settings.',
|
||||||
_HintAction('App-Einstellungen', BluetoothSystemSettings.openAppSettings),
|
_HintAction('App settings', BluetoothSystemSettings.openAppSettings),
|
||||||
),
|
),
|
||||||
LinkErrorReason.deviceSelectionRequired => (
|
LinkErrorReason.deviceSelectionRequired => (
|
||||||
'Kein gekoppeltes mLRS-Geraet gefunden. Bitte unten ein Geraet '
|
'No paired mLRS device found. Please select a device below.',
|
||||||
'auswaehlen.',
|
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
LinkErrorReason.connectionFailed => (
|
LinkErrorReason.connectionFailed => (
|
||||||
'Verbindung zum Geraet ist fehlgeschlagen.',
|
'Connection to the device failed.',
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
LinkErrorReason.unknown => ('Unbekannter Verbindungsfehler.', null),
|
LinkErrorReason.unknown => ('Unknown connection error.', null),
|
||||||
};
|
};
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
@@ -7,9 +9,11 @@ import '../providers/mission_meta_provider.dart';
|
|||||||
import 'missions_drones_panel.dart';
|
import 'missions_drones_panel.dart';
|
||||||
|
|
||||||
/// Statuszeile am unteren Bildschirmrand (HTML-Demonstrator: #bottomBar/
|
/// Statuszeile am unteren Bildschirmrand (HTML-Demonstrator: #bottomBar/
|
||||||
/// #statsRow). Details-Pille (mittig) oeffnet die Wegpunktliste; die
|
/// #statsRow). Details-Pille bleibt mittig und oeffnet die Wegpunktliste;
|
||||||
/// Mission-/Drohnen-Chips (links) oeffnen das Verwaltungsmenue mit dem
|
/// Mission-Chip links und Drohnen-Chip rechts oeffnen das Verwaltungsmenue
|
||||||
/// passenden Starttab (HTML-Demonstrator: #missionNameBtn/#droneNameBtn).
|
/// mit dem passenden Starttab (HTML-Demonstrator: #missionNameBtn/
|
||||||
|
/// #droneNameBtn). Warnungs- und Settings-Knopf sitzen neben dem
|
||||||
|
/// Drohnen-Chip rechts aussen.
|
||||||
/// Der Send-Button ist bewusst nicht Teil dieses Schritts - die dafuer
|
/// Der Send-Button ist bewusst nicht Teil dieses Schritts - die dafuer
|
||||||
/// noetige Upload-Anbindung existiert noch nicht.
|
/// noetige Upload-Anbindung existiert noch nicht.
|
||||||
class BottomStatsBar extends ConsumerWidget {
|
class BottomStatsBar extends ConsumerWidget {
|
||||||
@@ -44,6 +48,18 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
final missionName = ref.watch(currentMissionMetaProvider).name;
|
final missionName = ref.watch(currentMissionMetaProvider).name;
|
||||||
final droneName = ref.watch(activeDroneProfileProvider).name;
|
final droneName = ref.watch(activeDroneProfileProvider).name;
|
||||||
|
|
||||||
|
// Gespiegelter Randabstand (wie in PlanScreen fuer die Drehraeder, Doku:
|
||||||
|
// "Symmetrischer Randabstand auf beiden Seiten"): die groessere der
|
||||||
|
// beiden echten Seiten-Insets wird auf BEIDE Seiten angewendet, statt
|
||||||
|
// SafeAreas Standardverhalten (jede Seite nur ihren eigenen, ggf. 0)
|
||||||
|
// zu uebernehmen - sonst waere z.B. eine Frontkamera-Aussparung im
|
||||||
|
// Querformat nur auf einer Seite der Fusszeile sichtbar beruecksichtigt,
|
||||||
|
// je nachdem wie herum das Geraet gehalten wird.
|
||||||
|
final safeEdgeInset = math.max(
|
||||||
|
MediaQuery.of(context).padding.left,
|
||||||
|
MediaQuery.of(context).padding.right,
|
||||||
|
);
|
||||||
|
|
||||||
// Row statt Stack: ein Align/Center ohne width-/heightFactor nimmt sich
|
// Row statt Stack: ein Align/Center ohne width-/heightFactor nimmt sich
|
||||||
// immer den gesamten verfuegbaren Platz seines Elternwidgets (auch wenn
|
// immer den gesamten verfuegbaren Platz seines Elternwidgets (auch wenn
|
||||||
// sein Kind viel kleiner ist) - in einem Stack ohne begrenzende
|
// sein Kind viel kleiner ist) - in einem Stack ohne begrenzende
|
||||||
@@ -53,49 +69,57 @@ class BottomStatsBar extends ConsumerWidget {
|
|||||||
// dagegen zuverlaessig zentriert.
|
// dagegen zuverlaessig zentriert.
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
|
left: false,
|
||||||
|
right: false,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
12 + safeEdgeInset,
|
||||||
|
10,
|
||||||
|
12 + safeEdgeInset,
|
||||||
|
10,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
// Nur noch die Mission-Pille links - sie darf die gesamte
|
||||||
|
// verfuegbare Breite dieser Seite nutzen (Align loosent die
|
||||||
|
// Constraints statt sie wie Expanded zu erzwingen, das
|
||||||
|
// begrenzt auf die verfuegbare Breite, ohne einen kuenstlich
|
||||||
|
// kleinen Fixwert vorzugeben).
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: _NameChip(
|
child: _NameChip(
|
||||||
label: 'Mission: $missionName',
|
label: 'Mission: $missionName',
|
||||||
color: Colors.white.withValues(alpha: 0.12),
|
color: Colors.white.withValues(alpha: 0.12),
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
maxWidth: 160,
|
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (_) => const MissionsDronesPanel.missions(),
|
builder: (_) => const MissionsDronesPanel.missions(),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
|
||||||
Flexible(
|
|
||||||
child: _NameChip(
|
|
||||||
label: 'Drone: $droneName',
|
|
||||||
color: const Color(0xFF4D9DFF).withValues(alpha: 0.18),
|
|
||||||
textColor: const Color(0xFFCFE2FF),
|
|
||||||
maxWidth: 140,
|
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
|
||||||
builder: (_) => const MissionsDronesPanel.drones(),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
_detailsPill(),
|
_detailsPill(),
|
||||||
|
// Drohnen-Pille + Warnungs-/Settings-Knopf rechts. Die Pille
|
||||||
|
// sitzt in Flexible, damit sie bei langen Namen zu Gunsten der
|
||||||
|
// beiden festgroessen Rundknoepfe schrumpft/ellipsiert, statt
|
||||||
|
// sie aus dem sichtbaren Bereich zu draengen.
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: _NameChip(
|
||||||
|
label: 'Drone: $droneName',
|
||||||
|
color: const Color(0xFF4D9DFF).withValues(alpha: 0.18),
|
||||||
|
textColor: const Color(0xFFCFE2FF),
|
||||||
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (_) => const MissionsDronesPanel.drones(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
if (warnings.isNotEmpty) ...[
|
if (warnings.isNotEmpty) ...[
|
||||||
_WarningButton(onTap: onWarningsTap),
|
_WarningButton(onTap: onWarningsTap),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
@@ -215,21 +239,20 @@ class _NameChip extends StatelessWidget {
|
|||||||
required this.color,
|
required this.color,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.maxWidth,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
final Color color;
|
final Color color;
|
||||||
final Color textColor;
|
final Color textColor;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final double maxWidth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Kein fester maxWidth mehr - die Pille darf die verfuegbare Breite
|
||||||
|
// ihres Elternwidgets nutzen (Align/Flexible am Aufrufort begrenzen sie
|
||||||
|
// bereits auf die dort tatsaechlich verfuegbare Breite).
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -247,7 +270,6 @@ class _NameChip extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user