Ersetzt die einfachen Zahlen-TextFormFields im Drohnenprofil-Editor durch tastaturlose Swipe-Chips (Doku 3.5, HTML-Demonstrator: SwipeValueInput) - vertikales Ziehen aendert den Wert in grossen Schritten (stepBig), horizontales Ziehen in kleinen Schritten (stepSmall). Die Zugrichtung wird beim ersten Ueberschreiten einer 8px-Schwelle einmalig festgelegt und bleibt fuer den Rest der Geste gesperrt, damit eine leicht schraege Geste nicht zwischen den beiden Schrittweiten hin- und herspringt. Min/Max/ Schrittweiten je Feld 1:1 aus DRONE_EDITOR_GROUPS im HTML-Demonstrator uebernommen. Zusaetzlich ein "modified"-Punkt neben dem Label, sobald ein Wert vom Ausgangswert beim Oeffnen des Editors abweicht (HTML: .modified- dot/.chip-modified). lib/ui/widgets/swipe_value_input.dart: neuer, wiederverwendbarer Chip. Gesten-Konflikt mit der scrollbaren Formular-Liste (wichtigster Teil dieser Aenderung): Die Chips sitzen in einer vertikal scrollbaren ListView (elf Felder in Gruppen passen nicht auf einen Bildschirm). Ein GestureDetector.onPan* auf dem Chip verlor dabei auf einem echten Geraet durchgehend gegen die eigene Scroll-Geste der ListView - onPanStart/ onPanUpdate feuerten nie, weder fuer vertikale noch horizontale Zuege (mit Debug-Prints auf dem Pixel_10a-Emulator verifiziert). Behoben durch: - SwipeValueInput nutzt jetzt rohe Pointer-Events (Listener statt GestureDetector.onPan*) - diese werden unabhaengig vom Gesture-Arena- Ausgang immer zugestellt. - Ein neuer onDragActiveChanged-Callback informiert das Elternwidget, solange eine Geste aktiv ist; DroneProfileEditor sperrt darueber die ListView (NeverScrollableScrollPhysics) fuer die Dauer des Ziehens, so dass die Liste waehrenddessen nicht mitscrollt. Getestet: 7 Widget-Tests fuer SwipeValueInput (grosse/kleine Schritte, Richtungswechsel, Klemmen auf min/max, Rundung auf Nachkommastellen, "modified"-Punkt, sowie ein Regressionstest fuer exakt dieses Szenario - Chip innerhalb einer scrollbaren ListView). Alle 117 Tests sowie flutter analyze bestehen. Manuell auf dem Pixel_10a-Emulator verifiziert: vertikaler Zug auf "Min (stall)" aendert den Wert in 10er-Schritten ohne die Liste zu scrollen, horizontaler Zug auf "Cruise" in 1er-Schritten - beide mit korrekt aufleuchtendem "modified"-Indikator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
145 lines
4.2 KiB
Dart
145 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:dmc_app/ui/widgets/swipe_value_input.dart';
|
|
|
|
void main() {
|
|
Widget wrap({
|
|
required double value,
|
|
required double initialValue,
|
|
required void Function(double) onChanged,
|
|
double min = 0,
|
|
double max = 200,
|
|
double stepBig = 10,
|
|
double stepSmall = 1,
|
|
int decimals = 0,
|
|
}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: SwipeValueInput(
|
|
label: 'Test',
|
|
value: value,
|
|
initialValue: initialValue,
|
|
min: min,
|
|
max: max,
|
|
stepBig: stepBig,
|
|
stepSmall: stepSmall,
|
|
decimals: decimals,
|
|
unit: 'm',
|
|
onChanged: onChanged,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('vertikales Ziehen aendert den Wert in grossen Schritten (stepBig)',
|
|
(tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(wrap(value: 50, initialValue: 50, onChanged: changes.add));
|
|
|
|
// 3 Schritte a 14px nach oben -> +3*stepBig(10) = +30.
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(0, -42));
|
|
await tester.pump();
|
|
|
|
expect(changes, isNotEmpty);
|
|
expect(changes.last, 80);
|
|
});
|
|
|
|
testWidgets('horizontales Ziehen aendert den Wert in kleinen Schritten (stepSmall)',
|
|
(tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(wrap(value: 50, initialValue: 50, onChanged: changes.add));
|
|
|
|
// 3 Schritte a 14px nach rechts -> +3*stepSmall(1) = +3.
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(42, 0));
|
|
await tester.pump();
|
|
|
|
expect(changes, isNotEmpty);
|
|
expect(changes.last, 53);
|
|
});
|
|
|
|
testWidgets('nach links/unten ziehen verringert den Wert', (tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(wrap(value: 50, initialValue: 50, onChanged: changes.add));
|
|
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(0, 42));
|
|
await tester.pump();
|
|
|
|
expect(changes.last, 20);
|
|
});
|
|
|
|
testWidgets('klemmt auf max, wenn die grosse Schrittweite darueber hinausschiesst',
|
|
(tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(wrap(value: 190, initialValue: 190, onChanged: changes.add));
|
|
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(0, -140));
|
|
await tester.pump();
|
|
|
|
expect(changes.last, 200);
|
|
});
|
|
|
|
testWidgets('rundet auf die konfigurierte Nachkommastellenzahl', (tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(wrap(
|
|
value: 1.0,
|
|
initialValue: 1.0,
|
|
stepSmall: 0.5,
|
|
decimals: 1,
|
|
onChanged: changes.add,
|
|
));
|
|
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(14, 0));
|
|
await tester.pump();
|
|
|
|
expect(changes.last, 1.5);
|
|
});
|
|
|
|
testWidgets(
|
|
'aendert den Wert auch innerhalb einer scrollbaren Liste (Regression: '
|
|
'GestureDetector.onPan* verlor frueher gegen die Scroll-Geste der Liste)',
|
|
(tester) async {
|
|
final changes = <double>[];
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Scaffold(
|
|
body: ListView(
|
|
children: [
|
|
const SizedBox(height: 400),
|
|
SwipeValueInput(
|
|
label: 'Test',
|
|
value: 50,
|
|
initialValue: 50,
|
|
min: 0,
|
|
max: 200,
|
|
stepBig: 10,
|
|
stepSmall: 1,
|
|
unit: 'm',
|
|
onChanged: changes.add,
|
|
),
|
|
const SizedBox(height: 400),
|
|
],
|
|
),
|
|
),
|
|
));
|
|
|
|
await tester.drag(find.byType(SwipeValueInput), const Offset(0, -42));
|
|
await tester.pump();
|
|
|
|
expect(changes, isNotEmpty);
|
|
expect(changes.last, 80);
|
|
});
|
|
|
|
testWidgets('zeigt den "modified"-Punkt nur, wenn der Wert vom Ausgangswert abweicht',
|
|
(tester) async {
|
|
await tester.pumpWidget(wrap(value: 50, initialValue: 50, onChanged: (_) {}));
|
|
var opacity = tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity));
|
|
expect(opacity.opacity, 0);
|
|
|
|
await tester.pumpWidget(wrap(value: 60, initialValue: 50, onChanged: (_) {}));
|
|
opacity = tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity));
|
|
expect(opacity.opacity, 1);
|
|
});
|
|
}
|