2023-07-29 02:17:26 +00:00
|
|
|
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<QueueScreen> {
|
|
|
|
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<MediaItem> _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: <Widget>[
|
|
|
|
// 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<String>(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<bool>();
|
|
|
|
// 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),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|