Vollbild-Player: Schließgeste live an progress gekoppelt, symmetrisch zum Öffnen

This commit is contained in:
Hermes (Server)
2026-08-29 14:59:20 +02:00
parent 92148bc97c
commit e2d2a01965
2 changed files with 63 additions and 3 deletions
+40 -2
View File
@@ -15,6 +15,7 @@ import '../shared/theme.dart';
import 'audio_handler.dart';
import 'cover_farbe.dart';
import 'lrc.dart';
import 'player_expansion_controller.dart';
import 'queue_screen.dart';
/// Inhalt des Vollbild-Players — wird von HomeShell in ein per progress
@@ -49,8 +50,7 @@ class NowPlayingScreen extends StatelessWidget {
children: [
_VollbildLeiste(handler: handler, onLyrics: _showLyrics),
Expanded(
child: GestureDetector(
onVerticalDragStart: (_) {}, // s. Task 8
child: _SchliessGeste(
child: StreamBuilder<MediaItem?>(
stream: handler.mediaItem,
builder: (context, snapshot) {
@@ -153,6 +153,44 @@ class WanderndesCover extends StatelessWidget {
}
}
/// Schließt den Vollbild-Player per Wischgeste nach unten — symmetrisch zum
/// Öffnen am Mini-Player, live über denselben PlayerExpansionController.
/// Eigenes StatefulWidget, weil eine lokale Rohstrecke über die Gestendauer
/// mitgeführt werden muss (analog zum bisherigen `_versatz`-Muster in
/// mini_player.dart).
class _SchliessGeste extends StatefulWidget {
const _SchliessGeste({required this.child});
final Widget child;
@override
State<_SchliessGeste> createState() => _SchliessGesteState();
}
class _SchliessGesteState extends State<_SchliessGeste> {
double _strecke = 0;
@override
Widget build(BuildContext context) {
final controller = context.read<PlayerExpansionController>();
return GestureDetector(
// Ohne opaque behavior fängt die Geste keine Berührungen über den
// großflächigen, unbemalten Bereichen des Inhalts ab (z.B. über dem
// Cover-Platzhalter, der nur ein leeres SizedBox ist) — GestureDetector
// reagiert per Default (deferToChild) nur dort, wo ein Nachfahre
// tatsächlich etwas zeichnet (per Testlauf verifiziert).
behavior: HitTestBehavior.opaque,
onVerticalDragStart: (_) => _strecke = 0,
onVerticalDragUpdate: (details) {
_strecke += details.delta.dy;
controller.dragBy(details.delta.dy);
},
onVerticalDragEnd: (details) => controller.dragEnd(
context, _strecke, details.velocity.pixelsPerSecond.dy),
child: widget.child,
);
}
}
/// Die bisherigen AppBar-Actions (Songtext, Sleep-Timer, Warteschlange) als
/// eigene Kopfzeile statt echter AppBar — eine Route/Scaffold gibt es hier
/// nicht mehr.
+23 -1
View File
@@ -98,7 +98,7 @@ void main() {
// deshalb direkt in einen eigenen Scaffold gepumpt (Task 5 Step 3), statt
// eine Route zu pushen. Der Handler behält den zuvor gestarteten Titel in
// seinem mediaItem-Stream, den NowPlayingScreen darüber sofort sieht.
Future<void> oeffnePlayer(WidgetTester tester) async {
Future<PlayerExpansionController> oeffnePlayer(WidgetTester tester) async {
final expansion = PlayerExpansionController(vsync: tester);
addTearDown(expansion.dispose);
final providers = [
@@ -138,6 +138,7 @@ void main() {
),
);
await tester.pumpAndSettle();
return expansion;
}
Future<void> raeumeAuf(WidgetTester tester) async {
@@ -173,4 +174,25 @@ void main() {
await raeumeAuf(tester);
});
testWidgets('Ziehen nach unten im Vollbild-Player verringert progress live',
(tester) async {
// _TestShell (home_shell_expansion_test.dart) hat bewusst keinen echten
// MeloAudioHandler-Provider (siehe dortiger Kommentar) — ein echter
// NowPlayingScreen bräuchte den aber für context.read<MeloAudioHandler>().
// Dieser Test läuft deshalb hier, wo oeffnePlayer() bereits alle nötigen
// Provider bereitstellt (Plan Task 8 nennt now_playing_screen_test.dart
// selbst als Alternative zu home_shell_expansion_test.dart).
final expansion = await oeffnePlayer(tester);
expect(expansion.progress, 1.0);
await tester.drag(find.byType(NowPlayingScreen), const Offset(0, 150));
await tester.pump();
expect(expansion.progress, lessThan(1.0));
// dragEnd() (ausgelöst durch tester.drag) startet ein fling() — ohne
// pumpAndSettle bliebe dessen Ticker beim Testende aktiv.
await tester.pumpAndSettle();
await raeumeAuf(tester);
});
}