import 'dart:async'; import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/player.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:freezer/ui/player_screen.dart'; import 'package:freezer/ui/tiles.dart'; class QueueScreen extends StatefulWidget { @override _QueueScreenState createState() => _QueueScreenState(); } class _QueueScreenState extends State { late StreamSubscription _queueSub; static const _dismissibleBackground = DecoratedBox( decoration: BoxDecoration(color: Colors.red), child: Align( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.0), child: Icon(Icons.delete)), alignment: Alignment.centerLeft)); static const _dismissibleSecondaryBackground = DecoratedBox( decoration: BoxDecoration(color: Colors.red), child: Align( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.0), child: Icon(Icons.delete)), alignment: Alignment.centerRight)); /// Basically a simple list that keeps itself synchronized with [AudioHandler.queue], /// so that the [ReorderableListView] is updated instanly (as it should be) List _queueCache = []; @override void initState() { _queueCache = List.from(audioHandler.queue.value); // avoid shadow-copying _queueSub = audioHandler.queue.listen((newQueue) { print('got new queue!'); print(newQueue.map((e) => e.title).toList()); // avoid rebuilding if the cache has got the right update // if (listEquals(_queueCache, newQueue)) { // print('avoiding rebuilding queue since they are the same'); // return; // } _queueCache = List.from(newQueue); setState(() {}); }); super.initState(); } @override void dispose() { _queueSub.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Queue'.i18n), systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light, statusBarBrightness: Brightness.light, systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor, systemNavigationBarDividerColor: Color( Theme.of(context).scaffoldBackgroundColor.value - 0x00111111), systemNavigationBarIconBrightness: Brightness.light, ), // actions: [ // IconButton( // icon: Icon( // Icons.shuffle, // semanticLabel: "Shuffle".i18n, // ), // onPressed: () async { // await playerHelper.toggleShuffle(); // setState(() {}); // }, // ) // ], ), body: SafeArea( child: ReorderableListView.builder( onReorder: (oldIndex, newIndex) { if (oldIndex == playerHelper.queueIndex) return; _queueCache.reorder(oldIndex, newIndex); playerHelper.reorder(oldIndex, newIndex); setState(() {}); }, itemCount: _queueCache.length, itemBuilder: (BuildContext context, int i) { Track track = Track.fromMediaItem(audioHandler.queue.value[i]); return Dismissible( key: ValueKey(track.id), background: _dismissibleBackground, secondaryBackground: _dismissibleSecondaryBackground, onDismissed: (_) { audioHandler.removeQueueItemAt(i); setState(() => _queueCache.removeAt(i)); }, confirmDismiss: (_) { if (i == playerHelper.queueIndex) return audioHandler.skipToNext().then((value) => true); return Future.value(true); // final completer = Completer(); // ScaffoldMessenger.of(context).clearSnackBars(); // ScaffoldMessenger.of(context) // .showSnackBar(SnackBar( // behavior: SnackBarBehavior.floating, // content: Text('Song deleted from queue'), // action: SnackBarAction( // label: 'UNDO', // onPressed: () => completer.complete(false)))) // .closed // .then((value) { // if (value == SnackBarClosedReason.action) return; // completer.complete(true); // }); // return completer.future; }, child: TrackTile( track, onTap: () { pageViewLock = true; audioHandler.skipToQueueItem(i).then((value) { Navigator.of(context).pop(); pageViewLock = false; }); }, key: Key(track.id), ), ); }, ), ), ); } }