From 6687f2f67c2bb327c0f055969c616d47f2450617 Mon Sep 17 00:00:00 2001 From: "Hermes (Server)" Date: Sat, 29 Aug 2026 15:36:46 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20PlayerExpansionController.dragEnd=20r?= =?UTF-8?q?=C3=A4t=20=C3=96ffnen/Schlie=C3=9Fen=20nicht=20mehr=20aus=20Vor?= =?UTF-8?q?zeichen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug (Code-Review, P0-nah): dragEnd() wählte die Entscheidungslogik allein über das Vorzeichen von strecke. Bei bereits vollständig offenem Player klappte ein schwaches, versehentliches Fingerzittern nach oben (kleine negative Strecke, weit unter der Wisch-Schwelle) den Player ungewollt zu, weil das Vorzeichen in den ÖFFNEN-Zweig führte statt in den (korrekten) Schließen-Zweig, der bei so einer schwachen Geste keine Wirkung hätte. dragEnd() bekommt jetzt den Pflichtparameter `ausVollbild`, der den Aufrufer-Kontext explizit benennt statt ihn zu erraten. mini_player.dart (Öffnen-Geste) ruft mit ausVollbild: false, now_playing_screen.dart (Schließen-Geste) mit ausVollbild: true. Test zuerst (RED mit der alten 3-Parameter-Signatur reproduziert, dann GREEN nach dem Fix): test/player/player_expansion_controller_test.dart. Bestehende Tests (player_expansion_controller_test.dart, home_shell_expansion_test.dart) an die neue Signatur angepasst. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01SM6e73V9ZethPiqHSocxUA --- lib/player/mini_player.dart | 3 +- lib/player/now_playing_screen.dart | 3 +- lib/player/player_expansion_controller.dart | 26 ++++++++------ test/player/home_shell_expansion_test.dart | 2 +- .../player_expansion_controller_test.dart | 36 ++++++++++++++++--- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/lib/player/mini_player.dart b/lib/player/mini_player.dart index 6b4c59a..e37a81d 100644 --- a/lib/player/mini_player.dart +++ b/lib/player/mini_player.dart @@ -92,7 +92,8 @@ class _LeisteState extends State<_Leiste> { onVerticalDragEnd: (details) => context .read() .dragEnd(context, _vertikaleStrecke, - details.velocity.pixelsPerSecond.dy), + details.velocity.pixelsPerSecond.dy, + ausVollbild: false), onHorizontalDragUpdate: (details) => setState(() { _ziehtHorizontalGerade = true; _horizontalVersatz += details.delta.dx; diff --git a/lib/player/now_playing_screen.dart b/lib/player/now_playing_screen.dart index 4673629..20b67cc 100644 --- a/lib/player/now_playing_screen.dart +++ b/lib/player/now_playing_screen.dart @@ -185,7 +185,8 @@ class _SchliessGesteState extends State<_SchliessGeste> { controller.dragBy(details.delta.dy); }, onVerticalDragEnd: (details) => controller.dragEnd( - context, _strecke, details.velocity.pixelsPerSecond.dy), + context, _strecke, details.velocity.pixelsPerSecond.dy, + ausVollbild: true), child: widget.child, ); } diff --git a/lib/player/player_expansion_controller.dart b/lib/player/player_expansion_controller.dart index 82fcae4..954502f 100644 --- a/lib/player/player_expansion_controller.dart +++ b/lib/player/player_expansion_controller.dart @@ -35,16 +35,22 @@ class PlayerExpansionController extends ChangeNotifier { } /// [strecke]/[geschwindigkeit] wie in wischen.dart (negativ = nach oben). - /// Bei einer Öffnen-Geste (strecke <= 0) direkt über - /// [oeffnetVollbildMitStrecke] entschieden; bei einer Schließen-Geste - /// (strecke >= 0, im Vollbild-Player nach unten gezogen) über dieselbe - /// Funktion mit gespiegeltem Vorzeichen — "stark genug nach unten" ist - /// spiegelbildlich zu "stark genug nach oben". Fliegt danach mit der - /// echten Loslass-Geschwindigkeit als Startimpuls zum entschiedenen Ziel. - void dragEnd(BuildContext context, double strecke, double geschwindigkeit) { - final zielIstOffen = strecke <= 0 - ? oeffnetVollbildMitStrecke(strecke, geschwindigkeit) - : !oeffnetVollbildMitStrecke(-strecke, -geschwindigkeit); + /// [ausVollbild] benennt den Aufrufer-Kontext explizit, statt ihn aus dem + /// Vorzeichen von [strecke] zu raten (früherer Bug: bei bereits offenem + /// Player klappte ein schwaches, versehentliches Aufwärtszittern — negative + /// Strecke, weit unter der Schwelle — den Player zu, weil das Vorzeichen + /// allein in den ÖFFNEN-Zweig führte, der bei so einer schwachen Geste + /// `false` liefert). `false` (Mini-Player, Öffnen-Geste) entscheidet direkt + /// über [oeffnetVollbildMitStrecke]; `true` (Vollbild-Player, Schließen- + /// Geste) über dieselbe Funktion mit gespiegeltem Vorzeichen — "stark genug + /// nach unten" ist spiegelbildlich zu "stark genug nach oben". Fliegt + /// danach mit der echten Loslass-Geschwindigkeit als Startimpuls zum + /// entschiedenen Ziel. + void dragEnd(BuildContext context, double strecke, double geschwindigkeit, + {required bool ausVollbild}) { + final zielIstOffen = ausVollbild + ? !oeffnetVollbildMitStrecke(-strecke, -geschwindigkeit) + : oeffnetVollbildMitStrecke(strecke, geschwindigkeit); if (MediaQuery.disableAnimationsOf(context)) { _animation.value = zielIstOffen ? 1.0 : 0.0; return; diff --git a/test/player/home_shell_expansion_test.dart b/test/player/home_shell_expansion_test.dart index 220b28a..ba0962d 100644 --- a/test/player/home_shell_expansion_test.dart +++ b/test/player/home_shell_expansion_test.dart @@ -156,7 +156,7 @@ void main() { expect(controller.progress, closeTo(250 / oeffnenReferenzHoehe, 0.01)); expect(find.byType(BackdropFilter), findsOneWidget); - controller.dragEnd(ctx, -250, 0); + controller.dragEnd(ctx, -250, 0, ausVollbild: false); await tester.pumpAndSettle(); expect(controller.progress, 1.0); diff --git a/test/player/player_expansion_controller_test.dart b/test/player/player_expansion_controller_test.dart index e83a96a..1404062 100644 --- a/test/player/player_expansion_controller_test.dart +++ b/test/player/player_expansion_controller_test.dart @@ -98,7 +98,7 @@ void main() { })); controller.dragBy(-200); - controller.dragEnd(ctx, -200, 0); // Strecke > wischStreckeSchwelle (80) + controller.dragEnd(ctx, -200, 0, ausVollbild: false); // Strecke > wischStreckeSchwelle (80) await tester.pumpAndSettle(); expect(controller.progress, 1.0); controller.dispose(); @@ -116,7 +116,7 @@ void main() { })); controller.dragBy(-30); - controller.dragEnd(ctx, -30, 0); // unter wischStreckeSchwelle + controller.dragEnd(ctx, -30, 0, ausVollbild: false); // unter wischStreckeSchwelle await tester.pumpAndSettle(); expect(controller.progress, 0.0); controller.dispose(); @@ -136,7 +136,7 @@ void main() { controller.open(ctx); await tester.pumpAndSettle(); controller.dragBy(200); // nach unten, Betrag > Referenzhöhe/2 - controller.dragEnd(ctx, 200, 0); + controller.dragEnd(ctx, 200, 0, ausVollbild: true); await tester.pumpAndSettle(); expect(controller.progress, 0.0); controller.dispose(); @@ -156,7 +156,35 @@ void main() { controller.open(ctx); await tester.pumpAndSettle(); controller.dragBy(30); - controller.dragEnd(ctx, 30, 0); + controller.dragEnd(ctx, 30, 0, ausVollbild: true); + await tester.pumpAndSettle(); + expect(controller.progress, 1.0); + controller.dispose(); + }); + + testWidgets( + 'dragEnd bei bereits vollem Player: schwaches Fingerzittern nach oben ' + 'klappt ihn NICHT zu (asymmetrischer Bug — dragEnd darf die ' + 'Entscheidungsrichtung nicht aus dem Vorzeichen von strecke raten, ' + 'sondern muss den Aufrufer-Kontext kennen)', (tester) async { + late PlayerExpansionController controller; + late BuildContext ctx; + await tester.pumpWidget(mitMediaQuery(false, (context) { + ctx = context; + controller = PlayerExpansionController(vsync: tester); + return const SizedBox(); + })); + + controller.open(ctx); + await tester.pumpAndSettle(); + // Der Player ist voll offen (progress==1). Der Nutzer verrutscht beim + // Versuch, einen Knopf zu treffen, leicht nach oben — eine winzige, + // negative Strecke weit unter der Wisch-Schwelle. Das darf gar keine + // Zustandsänderung auslösen, weil die Geste viel zu schwach ist — schon + // gar nicht ein Zuklappen, das würde "nach oben ziehen schließt den + // offenen Player" bedeuten, was es nicht tut. + controller.dragBy(-5); + controller.dragEnd(ctx, -5, 0, ausVollbild: true); await tester.pumpAndSettle(); expect(controller.progress, 1.0); controller.dispose();