2021-08-29 22:25:18 +00:00
|
|
|
import 'dart:math';
|
|
|
|
|
2020-11-09 21:05:47 +00:00
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
2020-11-28 21:32:17 +00:00
|
|
|
import 'package:flutter/foundation.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:audio_service/audio_service.dart';
|
2020-10-16 18:54:04 +00:00
|
|
|
import 'package:flutter/services.dart';
|
2021-08-29 22:25:18 +00:00
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
2021-04-16 18:21:35 +00:00
|
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
2020-10-19 19:28:45 +00:00
|
|
|
import 'package:freezer/api/cache.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
import 'package:freezer/api/deezer.dart';
|
2020-11-28 21:32:17 +00:00
|
|
|
import 'package:freezer/api/download.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
import 'package:freezer/api/player.dart';
|
2020-10-16 18:54:04 +00:00
|
|
|
import 'package:freezer/settings.dart';
|
2020-09-18 17:36:41 +00:00
|
|
|
import 'package:freezer/translations.i18n.dart';
|
2020-10-19 19:28:45 +00:00
|
|
|
import 'package:freezer/ui/elements.dart';
|
2020-11-09 21:05:47 +00:00
|
|
|
import 'package:freezer/ui/lyrics.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
import 'package:freezer/ui/menu.dart';
|
2020-07-18 21:45:48 +00:00
|
|
|
import 'package:freezer/ui/settings_screen.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
import 'package:freezer/ui/tiles.dart';
|
2020-08-13 17:39:22 +00:00
|
|
|
import 'package:just_audio/just_audio.dart';
|
2020-07-19 12:41:05 +00:00
|
|
|
import 'package:marquee/marquee.dart';
|
2020-11-09 21:05:47 +00:00
|
|
|
import 'package:palette_generator/palette_generator.dart';
|
2020-06-23 19:23:12 +00:00
|
|
|
|
|
|
|
import 'cached_image.dart';
|
|
|
|
import '../api/definitions.dart';
|
|
|
|
import 'player_bar.dart';
|
|
|
|
|
2020-09-18 17:36:41 +00:00
|
|
|
import 'dart:ui';
|
2021-02-09 20:14:14 +00:00
|
|
|
import 'dart:convert';
|
2020-09-18 17:36:41 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
2021-02-09 20:14:14 +00:00
|
|
|
//Changing item in queue view and pressing back causes the pageView to skip song
|
|
|
|
bool pageViewLock = false;
|
2020-11-28 21:32:17 +00:00
|
|
|
|
2021-03-16 19:35:50 +00:00
|
|
|
//So can be updated when going back from lyrics
|
2021-09-01 12:38:32 +00:00
|
|
|
late Function updateColor;
|
2021-03-16 19:35:50 +00:00
|
|
|
|
2020-06-23 19:23:12 +00:00
|
|
|
class PlayerScreen extends StatefulWidget {
|
2021-08-29 22:25:18 +00:00
|
|
|
static const _blurStrength = 50.0;
|
|
|
|
|
2020-06-23 19:23:12 +00:00
|
|
|
@override
|
|
|
|
_PlayerScreenState createState() => _PlayerScreenState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PlayerScreenState extends State<PlayerScreen> {
|
2021-09-01 12:38:32 +00:00
|
|
|
LinearGradient? _bgGradient;
|
|
|
|
late StreamSubscription _mediaItemSub;
|
|
|
|
late StreamSubscription _playerStateSub;
|
|
|
|
ImageProvider? _blurImage;
|
2021-08-29 22:25:18 +00:00
|
|
|
bool _wasConnected = true;
|
2020-11-09 21:05:47 +00:00
|
|
|
|
|
|
|
//Calculate background color
|
2020-11-28 21:32:17 +00:00
|
|
|
Future _updateColor() async {
|
2021-09-01 12:38:32 +00:00
|
|
|
if (!settings.colorGradientBackground! && !settings.blurPlayerBackground!)
|
2020-11-09 21:05:47 +00:00
|
|
|
return;
|
2021-08-29 22:25:18 +00:00
|
|
|
final imageProvider = CachedNetworkImageProvider(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.mediaItem.value!.extras!['thumb'] ??
|
|
|
|
audioHandler.mediaItem.value!.artUri as String);
|
2021-02-09 20:14:14 +00:00
|
|
|
//BG Image
|
2021-09-01 12:38:32 +00:00
|
|
|
if (settings.blurPlayerBackground!)
|
2021-08-29 22:25:18 +00:00
|
|
|
setState(() => _blurImage = imageProvider);
|
2021-02-09 20:14:14 +00:00
|
|
|
|
2021-09-01 12:38:32 +00:00
|
|
|
if (settings.colorGradientBackground!) {
|
2021-08-29 22:25:18 +00:00
|
|
|
//Run in isolate
|
|
|
|
PaletteGenerator palette =
|
|
|
|
await PaletteGenerator.fromImageProvider(imageProvider);
|
2020-11-28 21:32:17 +00:00
|
|
|
|
2021-02-09 20:14:14 +00:00
|
|
|
setState(() => _bgGradient = LinearGradient(
|
2021-04-05 20:27:54 +00:00
|
|
|
begin: Alignment.topCenter,
|
|
|
|
end: Alignment.bottomCenter,
|
|
|
|
colors: [
|
2021-09-01 12:38:32 +00:00
|
|
|
palette.dominantColor!.color.withOpacity(0.7),
|
2021-04-05 20:27:54 +00:00
|
|
|
Color.fromARGB(0, 0, 0, 0)
|
|
|
|
],
|
|
|
|
stops: [
|
|
|
|
0.0,
|
|
|
|
0.6
|
|
|
|
]));
|
2021-02-09 20:14:14 +00:00
|
|
|
}
|
2020-11-09 21:05:47 +00:00
|
|
|
}
|
|
|
|
|
2021-08-29 22:25:18 +00:00
|
|
|
void _playbackStateChanged() {
|
2021-09-01 12:38:32 +00:00
|
|
|
// if (audioHandler.mediaItem.value == null) {
|
|
|
|
// //playerHelper.startService();
|
|
|
|
// setState(() => _wasConnected = false);
|
|
|
|
// } else if (!_wasConnected) setState(() => _wasConnected = true);
|
2021-08-29 22:25:18 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 21:05:47 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
2021-02-09 20:14:14 +00:00
|
|
|
Future.delayed(Duration(milliseconds: 600), _updateColor);
|
2021-08-29 22:25:18 +00:00
|
|
|
_playbackStateChanged();
|
2021-09-01 12:38:32 +00:00
|
|
|
_mediaItemSub = audioHandler.mediaItem.listen((event) {
|
2021-08-29 22:25:18 +00:00
|
|
|
_playbackStateChanged();
|
2020-11-28 21:32:17 +00:00
|
|
|
_updateColor();
|
2020-11-09 21:05:47 +00:00
|
|
|
});
|
2021-08-29 22:25:18 +00:00
|
|
|
_playerStateSub =
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.playbackState.listen((_) => _playbackStateChanged());
|
2021-03-16 19:35:50 +00:00
|
|
|
|
|
|
|
updateColor = this._updateColor;
|
2020-11-09 21:05:47 +00:00
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
2020-10-16 18:54:04 +00:00
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-08-29 22:25:18 +00:00
|
|
|
_mediaItemSub.cancel();
|
|
|
|
_playerStateSub.cancel();
|
2020-10-16 18:54:04 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
2020-06-23 19:23:12 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-08-29 22:25:18 +00:00
|
|
|
final hasBackground =
|
2021-09-01 12:38:32 +00:00
|
|
|
settings.blurPlayerBackground! || settings.colorGradientBackground!;
|
2021-08-29 22:25:18 +00:00
|
|
|
final color = hasBackground
|
|
|
|
? Colors.transparent
|
|
|
|
: Theme.of(context).scaffoldBackgroundColor;
|
|
|
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
|
|
value: SystemUiOverlayStyle(
|
|
|
|
statusBarColor: color,
|
|
|
|
statusBarBrightness: Brightness.light,
|
|
|
|
statusBarIconBrightness: Brightness.light,
|
|
|
|
systemNavigationBarIconBrightness: Brightness.light,
|
|
|
|
systemNavigationBarColor: color,
|
|
|
|
systemNavigationBarDividerColor: color,
|
|
|
|
),
|
|
|
|
child: Stack(
|
|
|
|
children: [
|
|
|
|
if (hasBackground)
|
|
|
|
Positioned.fill(
|
|
|
|
child: ImageFiltered(
|
|
|
|
imageFilter: ImageFilter.blur(
|
|
|
|
sigmaX: PlayerScreen._blurStrength,
|
|
|
|
sigmaY: PlayerScreen._blurStrength,
|
|
|
|
tileMode: TileMode.mirror),
|
|
|
|
child: DecoratedBox(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
gradient: _bgGradient,
|
|
|
|
image: _blurImage == null
|
|
|
|
? null
|
|
|
|
: DecorationImage(
|
2021-09-01 12:38:32 +00:00
|
|
|
image: _blurImage!,
|
2021-08-29 22:25:18 +00:00
|
|
|
fit: BoxFit.cover,
|
|
|
|
colorFilter: ColorFilter.mode(
|
|
|
|
Colors.white.withOpacity(0.5),
|
|
|
|
BlendMode.dstATop))),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Scaffold(
|
|
|
|
backgroundColor: hasBackground ? Colors.transparent : null,
|
|
|
|
body: _wasConnected
|
|
|
|
? SafeArea(
|
|
|
|
child: OrientationBuilder(
|
|
|
|
builder: (context, orientation) =>
|
|
|
|
orientation == Orientation.landscape
|
|
|
|
? PlayerScreenHorizontal()
|
|
|
|
: PlayerScreenVertical(),
|
2021-02-09 20:14:14 +00:00
|
|
|
),
|
2021-08-29 22:25:18 +00:00
|
|
|
)
|
|
|
|
: Center(child: CircularProgressIndicator()),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
2020-07-18 21:45:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Landscape
|
|
|
|
class PlayerScreenHorizontal extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_PlayerScreenHorizontalState createState() => _PlayerScreenHorizontalState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
|
|
children: <Widget>[
|
|
|
|
Padding(
|
2020-10-14 19:09:16 +00:00
|
|
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
|
|
|
|
child: Container(
|
2021-04-05 20:27:54 +00:00
|
|
|
width: ScreenUtil().setWidth(500),
|
|
|
|
child: Stack(
|
|
|
|
children: <Widget>[
|
|
|
|
BigAlbumArt(),
|
|
|
|
],
|
2020-10-14 19:09:16 +00:00
|
|
|
),
|
2021-04-05 20:27:54 +00:00
|
|
|
),
|
2020-07-18 21:45:48 +00:00
|
|
|
),
|
|
|
|
//Right side
|
|
|
|
SizedBox(
|
|
|
|
width: ScreenUtil().setWidth(500),
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
|
|
|
|
child: Container(
|
|
|
|
child: PlayerScreenTopRow(
|
2021-04-05 20:27:54 +00:00
|
|
|
textSize: ScreenUtil().setSp(24),
|
|
|
|
iconSize: ScreenUtil().setSp(36),
|
|
|
|
textWidth: ScreenUtil().setWidth(350),
|
|
|
|
short: true),
|
|
|
|
)),
|
2020-07-18 21:45:48 +00:00
|
|
|
Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
2020-07-19 12:41:05 +00:00
|
|
|
Container(
|
2021-04-05 20:27:54 +00:00
|
|
|
height: ScreenUtil().setSp(50),
|
2021-09-01 12:38:32 +00:00
|
|
|
child: audioHandler
|
|
|
|
.mediaItem.value!.displayTitle!.length >=
|
2021-04-05 20:27:54 +00:00
|
|
|
22
|
|
|
|
? Marquee(
|
2021-09-01 12:38:32 +00:00
|
|
|
text: audioHandler.mediaItem.value!.displayTitle!,
|
2021-04-05 20:27:54 +00:00
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(40),
|
|
|
|
fontWeight: FontWeight.bold),
|
|
|
|
blankSpace: 32.0,
|
|
|
|
startPadding: 10.0,
|
|
|
|
accelerationDuration: Duration(seconds: 1),
|
|
|
|
pauseAfterRound: Duration(seconds: 2),
|
|
|
|
)
|
|
|
|
: Text(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.mediaItem.value!.displayTitle!,
|
2021-04-05 20:27:54 +00:00
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(40),
|
|
|
|
fontWeight: FontWeight.bold),
|
|
|
|
)),
|
2021-08-29 22:25:18 +00:00
|
|
|
const SizedBox(height: 4.0),
|
2020-07-18 21:45:48 +00:00
|
|
|
Text(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.mediaItem.value!.displaySubtitle ?? '',
|
2020-07-18 21:45:48 +00:00
|
|
|
maxLines: 1,
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
overflow: TextOverflow.clip,
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(32),
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
Container(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
|
|
|
child: SeekBar(),
|
|
|
|
),
|
2020-10-19 19:28:45 +00:00
|
|
|
PlaybackControls(ScreenUtil().setSp(60)),
|
2020-07-18 21:45:48 +00:00
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
|
|
|
|
child: Container(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
|
|
|
child: Row(
|
2020-06-24 14:52:53 +00:00
|
|
|
mainAxisSize: MainAxisSize.max,
|
2020-07-18 21:45:48 +00:00
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
2020-06-24 14:52:53 +00:00
|
|
|
children: <Widget>[
|
2020-07-18 21:45:48 +00:00
|
|
|
IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.subtitles,
|
|
|
|
size: ScreenUtil().setWidth(32),
|
|
|
|
semanticLabel: "Lyrics".i18n,
|
|
|
|
),
|
2020-07-18 21:45:48 +00:00
|
|
|
onPressed: () {
|
2020-11-09 21:05:47 +00:00
|
|
|
Navigator.of(context).push(MaterialPageRoute(
|
2021-09-01 12:38:32 +00:00
|
|
|
builder: (context) => LyricsScreen()));
|
2020-07-18 21:45:48 +00:00
|
|
|
},
|
2020-06-24 14:52:53 +00:00
|
|
|
),
|
2020-11-28 21:32:17 +00:00
|
|
|
QualityInfoWidget(),
|
2020-11-01 19:23:24 +00:00
|
|
|
RepeatButton(ScreenUtil().setWidth(32)),
|
2020-11-28 21:32:17 +00:00
|
|
|
PlayerMenuButton()
|
2020-07-18 21:45:48 +00:00
|
|
|
],
|
|
|
|
),
|
2021-04-05 20:27:54 +00:00
|
|
|
))
|
2020-07-18 21:45:48 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 19:23:12 +00:00
|
|
|
|
2020-07-18 21:45:48 +00:00
|
|
|
//Portrait
|
|
|
|
class PlayerScreenVertical extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_PlayerScreenVerticalState createState() => _PlayerScreenVerticalState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Column(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
|
|
|
Padding(
|
2021-04-05 20:27:54 +00:00
|
|
|
padding: EdgeInsets.fromLTRB(30, 4, 16, 0),
|
|
|
|
child: PlayerScreenTopRow()),
|
2020-07-18 21:45:48 +00:00
|
|
|
Padding(
|
2021-08-29 22:25:18 +00:00
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
|
|
|
child: SizedBox(
|
|
|
|
child: BigAlbumArt(),
|
|
|
|
width: min(MediaQuery.of(context).size.width,
|
|
|
|
MediaQuery.of(context).size.height) *
|
|
|
|
0.9,
|
|
|
|
height: min(MediaQuery.of(context).size.width,
|
|
|
|
MediaQuery.of(context).size.height) *
|
|
|
|
0.9,
|
2020-10-19 19:28:45 +00:00
|
|
|
),
|
2020-07-18 21:45:48 +00:00
|
|
|
),
|
2021-08-29 22:25:18 +00:00
|
|
|
const SizedBox(height: 4.0),
|
2020-07-18 21:45:48 +00:00
|
|
|
Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
2020-07-19 12:41:05 +00:00
|
|
|
Container(
|
2021-04-05 20:27:54 +00:00
|
|
|
height: ScreenUtil().setSp(80),
|
2021-09-01 12:38:32 +00:00
|
|
|
child: audioHandler.mediaItem.value!.displayTitle!.length >= 26
|
2021-04-05 20:27:54 +00:00
|
|
|
? Marquee(
|
2021-09-01 12:38:32 +00:00
|
|
|
text: audioHandler.mediaItem.value!.displayTitle!,
|
2021-04-05 20:27:54 +00:00
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(64),
|
|
|
|
fontWeight: FontWeight.bold),
|
|
|
|
blankSpace: 32.0,
|
|
|
|
startPadding: 10.0,
|
|
|
|
accelerationDuration: Duration(seconds: 1),
|
|
|
|
pauseAfterRound: Duration(seconds: 2),
|
|
|
|
)
|
|
|
|
: Text(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.mediaItem.value!.displayTitle!,
|
2021-04-05 20:27:54 +00:00
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(64),
|
|
|
|
fontWeight: FontWeight.bold),
|
|
|
|
)),
|
2021-08-29 22:25:18 +00:00
|
|
|
const SizedBox(height: 4),
|
2020-07-18 21:45:48 +00:00
|
|
|
Text(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.mediaItem.value!.displaySubtitle ?? '',
|
2020-07-18 21:45:48 +00:00
|
|
|
maxLines: 1,
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
overflow: TextOverflow.clip,
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: ScreenUtil().setSp(52),
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
SeekBar(),
|
2020-10-19 19:28:45 +00:00
|
|
|
PlaybackControls(ScreenUtil().setWidth(100)),
|
2020-07-18 21:45:48 +00:00
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 16.0),
|
|
|
|
child: Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
|
|
|
IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.subtitles,
|
|
|
|
size: ScreenUtil().setWidth(46),
|
|
|
|
semanticLabel: "Lyrics".i18n,
|
|
|
|
),
|
2021-03-16 19:35:50 +00:00
|
|
|
onPressed: () async {
|
|
|
|
//Fix bottom buttons
|
|
|
|
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
2021-04-05 20:27:54 +00:00
|
|
|
systemNavigationBarColor:
|
2021-09-01 12:38:32 +00:00
|
|
|
settings.themeData!.bottomAppBarColor,
|
2021-04-05 20:27:54 +00:00
|
|
|
statusBarColor: Colors.transparent));
|
2021-03-16 19:35:50 +00:00
|
|
|
|
2021-09-01 12:38:32 +00:00
|
|
|
await Navigator.of(context).push(
|
|
|
|
MaterialPageRoute(builder: (context) => LyricsScreen()));
|
2021-03-16 19:35:50 +00:00
|
|
|
|
|
|
|
updateColor();
|
2020-07-18 21:45:48 +00:00
|
|
|
},
|
|
|
|
),
|
2021-04-16 18:21:35 +00:00
|
|
|
IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.file_download,
|
|
|
|
semanticLabel: "Download".i18n,
|
|
|
|
),
|
2021-04-16 18:21:35 +00:00
|
|
|
onPressed: () async {
|
2021-09-01 12:38:32 +00:00
|
|
|
Track t = Track.fromMediaItem(audioHandler.mediaItem.value!);
|
2021-07-23 17:14:21 +00:00
|
|
|
if (await downloadManager.addOfflineTrack(t,
|
|
|
|
private: false,
|
|
|
|
context: context,
|
|
|
|
isSingleton: true) !=
|
|
|
|
false)
|
2021-04-16 18:21:35 +00:00
|
|
|
Fluttertoast.showToast(
|
2021-07-23 17:14:21 +00:00
|
|
|
msg: 'Downloads added!'.i18n,
|
|
|
|
gravity: ToastGravity.BOTTOM,
|
|
|
|
toastLength: Toast.LENGTH_SHORT);
|
2021-04-16 18:21:35 +00:00
|
|
|
},
|
|
|
|
),
|
2020-11-28 21:32:17 +00:00
|
|
|
QualityInfoWidget(),
|
2020-11-01 19:23:24 +00:00
|
|
|
RepeatButton(ScreenUtil().setWidth(46)),
|
2020-11-28 21:32:17 +00:00
|
|
|
PlayerMenuButton()
|
2020-07-18 21:45:48 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
2020-06-23 19:23:12 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-28 21:32:17 +00:00
|
|
|
class QualityInfoWidget extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_QualityInfoWidgetState createState() => _QualityInfoWidgetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _QualityInfoWidgetState extends State<QualityInfoWidget> {
|
|
|
|
String value = '';
|
2021-09-01 12:38:32 +00:00
|
|
|
late StreamSubscription streamSubscription;
|
2020-11-28 21:32:17 +00:00
|
|
|
|
|
|
|
//Load data from native
|
|
|
|
void _load() async {
|
2021-09-01 12:38:32 +00:00
|
|
|
if (audioHandler.mediaItem.value == null) return;
|
|
|
|
Map? data = await DownloadManager.platform.invokeMethod(
|
|
|
|
"getStreamInfo", {"id": audioHandler.mediaItem.value!.id});
|
2020-11-28 21:32:17 +00:00
|
|
|
//N/A
|
|
|
|
if (data == null) {
|
|
|
|
setState(() => value = '');
|
|
|
|
//If not show, try again later
|
2021-09-01 12:38:32 +00:00
|
|
|
if (audioHandler.mediaItem.value!.extras!['show'] == null)
|
2020-11-28 21:32:17 +00:00
|
|
|
Future.delayed(Duration(milliseconds: 200), _load);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//Update
|
|
|
|
StreamQualityInfo info = StreamQualityInfo.fromJson(data);
|
|
|
|
setState(() {
|
2021-04-05 20:27:54 +00:00
|
|
|
value =
|
2021-09-01 12:38:32 +00:00
|
|
|
'${info.format} ${info.bitrate(audioHandler.mediaItem.value!.duration)}kbps';
|
2020-11-28 21:32:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
_load();
|
2021-09-01 12:38:32 +00:00
|
|
|
streamSubscription = audioHandler.mediaItem.listen((event) async {
|
|
|
|
_load();
|
|
|
|
});
|
2020-11-28 21:32:17 +00:00
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-09-01 12:38:32 +00:00
|
|
|
streamSubscription.cancel();
|
2020-11-28 21:32:17 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-04-04 22:58:39 +00:00
|
|
|
return TextButton(
|
2020-11-28 21:32:17 +00:00
|
|
|
child: Text(value),
|
|
|
|
onPressed: () {
|
2021-04-05 20:27:54 +00:00
|
|
|
Navigator.of(context)
|
|
|
|
.push(MaterialPageRoute(builder: (context) => QualitySettings()));
|
2020-11-28 21:32:17 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PlayerMenuButton extends StatelessWidget {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.more_vert,
|
|
|
|
size: ScreenUtil().setWidth(46),
|
|
|
|
semanticLabel: "Options".i18n,
|
|
|
|
),
|
2020-11-28 21:32:17 +00:00
|
|
|
onPressed: () {
|
2021-09-01 12:38:32 +00:00
|
|
|
final currentMediaItem = audioHandler.mediaItem.value!;
|
|
|
|
Track t = Track.fromMediaItem(currentMediaItem);
|
2020-12-27 18:33:59 +00:00
|
|
|
MenuSheet m = MenuSheet(context, navigateCallback: () {
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
});
|
2021-09-01 12:38:32 +00:00
|
|
|
if (currentMediaItem.extras!['show'] == null)
|
2020-12-14 17:29:28 +00:00
|
|
|
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
2020-11-28 21:32:17 +00:00
|
|
|
else
|
|
|
|
m.defaultShowEpisodeMenu(
|
2021-09-01 12:38:32 +00:00
|
|
|
Show.fromJson(jsonDecode(currentMediaItem.extras!['show'])),
|
|
|
|
ShowEpisode.fromMediaItem(currentMediaItem),
|
2021-04-05 20:27:54 +00:00
|
|
|
options: [m.sleepTimer(), m.wakelock()]);
|
2020-11-28 21:32:17 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-01 19:23:24 +00:00
|
|
|
class RepeatButton extends StatefulWidget {
|
2020-10-19 19:28:45 +00:00
|
|
|
final double iconSize;
|
2021-09-01 12:38:32 +00:00
|
|
|
RepeatButton(this.iconSize, {Key? key}) : super(key: key);
|
2020-10-19 19:28:45 +00:00
|
|
|
|
|
|
|
@override
|
2020-11-01 19:23:24 +00:00
|
|
|
_RepeatButtonState createState() => _RepeatButtonState();
|
2020-10-19 19:28:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-01 19:23:24 +00:00
|
|
|
class _RepeatButtonState extends State<RepeatButton> {
|
2021-08-29 22:25:18 +00:00
|
|
|
// ignore: missing_return
|
2020-10-19 19:28:45 +00:00
|
|
|
Icon get repeatIcon {
|
|
|
|
switch (playerHelper.repeatType) {
|
|
|
|
case LoopMode.off:
|
|
|
|
return Icon(
|
2021-07-23 17:14:21 +00:00
|
|
|
Icons.repeat,
|
|
|
|
size: widget.iconSize,
|
|
|
|
semanticLabel: "Repeat off".i18n,
|
2020-10-19 19:28:45 +00:00
|
|
|
);
|
|
|
|
case LoopMode.all:
|
|
|
|
return Icon(
|
2021-07-23 17:14:21 +00:00
|
|
|
Icons.repeat,
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
size: widget.iconSize,
|
|
|
|
semanticLabel: "Repeat".i18n,
|
2020-10-19 19:28:45 +00:00
|
|
|
);
|
|
|
|
case LoopMode.one:
|
|
|
|
return Icon(
|
|
|
|
Icons.repeat_one,
|
|
|
|
color: Theme.of(context).primaryColor,
|
2021-07-02 16:28:59 +00:00
|
|
|
size: widget.iconSize,
|
|
|
|
semanticLabel: "Repeat one".i18n,
|
2020-10-19 19:28:45 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-01 19:23:24 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return IconButton(
|
|
|
|
icon: repeatIcon,
|
|
|
|
onPressed: () async {
|
|
|
|
await playerHelper.changeRepeat();
|
|
|
|
setState(() {});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PlaybackControls extends StatefulWidget {
|
|
|
|
final double iconSize;
|
2021-09-01 12:38:32 +00:00
|
|
|
PlaybackControls(this.iconSize, {Key? key}) : super(key: key);
|
2020-11-01 19:23:24 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
_PlaybackControlsState createState() => _PlaybackControlsState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PlaybackControlsState extends State<PlaybackControls> {
|
2020-10-19 19:28:45 +00:00
|
|
|
Icon get libraryIcon {
|
2021-04-05 20:27:54 +00:00
|
|
|
if (cache.checkTrackFavorite(
|
2021-09-01 12:38:32 +00:00
|
|
|
Track.fromMediaItem(audioHandler.mediaItem.value!))) {
|
2021-07-23 17:14:21 +00:00
|
|
|
return Icon(
|
|
|
|
Icons.favorite,
|
|
|
|
size: widget.iconSize * 0.64,
|
|
|
|
semanticLabel: "Unlove".i18n,
|
|
|
|
);
|
2020-10-19 19:28:45 +00:00
|
|
|
}
|
2021-07-23 17:14:21 +00:00
|
|
|
return Icon(
|
|
|
|
Icons.favorite_border,
|
|
|
|
size: widget.iconSize * 0.64,
|
|
|
|
semanticLabel: "Love".i18n,
|
|
|
|
);
|
2020-10-19 19:28:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Padding(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
children: [
|
|
|
|
IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.sentiment_very_dissatisfied,
|
|
|
|
size: ScreenUtil().setWidth(46),
|
|
|
|
semanticLabel: "Dislike".i18n,
|
|
|
|
),
|
2020-11-01 19:23:24 +00:00
|
|
|
onPressed: () async {
|
2021-09-01 12:38:32 +00:00
|
|
|
await deezerAPI.dislikeTrack(audioHandler.mediaItem.value!.id);
|
2021-04-05 20:27:54 +00:00
|
|
|
if (playerHelper.queueIndex <
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.queue.value.length - 1) {
|
|
|
|
audioHandler.skipToNext();
|
2020-11-01 19:23:24 +00:00
|
|
|
}
|
2021-04-05 20:27:54 +00:00
|
|
|
}),
|
2020-10-19 19:28:45 +00:00
|
|
|
PrevNextButton(widget.iconSize, prev: true),
|
|
|
|
PlayPauseButton(widget.iconSize * 1.25),
|
|
|
|
PrevNextButton(widget.iconSize),
|
|
|
|
IconButton(
|
|
|
|
icon: libraryIcon,
|
|
|
|
onPressed: () async {
|
2021-04-05 20:27:54 +00:00
|
|
|
if (cache.libraryTracks == null) cache.libraryTracks = [];
|
2020-10-19 19:28:45 +00:00
|
|
|
|
2021-04-05 20:27:54 +00:00
|
|
|
if (cache.checkTrackFavorite(
|
2021-09-01 12:38:32 +00:00
|
|
|
Track.fromMediaItem(audioHandler.mediaItem.value!))) {
|
2020-10-19 19:28:45 +00:00
|
|
|
//Remove from library
|
2021-09-01 12:38:32 +00:00
|
|
|
setState(() => cache.libraryTracks!
|
|
|
|
.remove(audioHandler.mediaItem.value!.id));
|
2021-04-05 20:27:54 +00:00
|
|
|
await deezerAPI
|
2021-09-01 12:38:32 +00:00
|
|
|
.removeFavorite(audioHandler.mediaItem.value!.id);
|
2020-10-19 19:28:45 +00:00
|
|
|
await cache.save();
|
|
|
|
} else {
|
|
|
|
//Add
|
2021-04-05 20:27:54 +00:00
|
|
|
setState(() =>
|
2021-09-01 12:38:32 +00:00
|
|
|
cache.libraryTracks!.add(audioHandler.mediaItem.value!.id));
|
2021-04-05 20:27:54 +00:00
|
|
|
await deezerAPI
|
2021-09-01 12:38:32 +00:00
|
|
|
.addFavoriteTrack(audioHandler.mediaItem.value!.id);
|
2020-10-19 19:28:45 +00:00
|
|
|
await cache.save();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-15 18:37:36 +00:00
|
|
|
class BigAlbumArt extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_BigAlbumArtState createState() => _BigAlbumArtState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _BigAlbumArtState extends State<BigAlbumArt> {
|
|
|
|
PageController _pageController = PageController(
|
|
|
|
initialPage: playerHelper.queueIndex,
|
|
|
|
);
|
2021-09-01 12:38:32 +00:00
|
|
|
StreamSubscription? _currentItemSub;
|
2020-10-15 18:37:36 +00:00
|
|
|
bool _animationLock = true;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
2021-09-01 12:38:32 +00:00
|
|
|
_currentItemSub = audioHandler.mediaItem.listen((event) async {
|
2020-10-15 18:37:36 +00:00
|
|
|
_animationLock = true;
|
2021-04-05 20:27:54 +00:00
|
|
|
await _pageController.animateToPage(playerHelper.queueIndex,
|
|
|
|
duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
|
2020-10-15 18:37:36 +00:00
|
|
|
_animationLock = false;
|
|
|
|
});
|
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-09-01 12:38:32 +00:00
|
|
|
_currentItemSub?.cancel();
|
2020-10-15 18:37:36 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2020-10-19 19:28:45 +00:00
|
|
|
return GestureDetector(
|
|
|
|
onVerticalDragUpdate: (DragUpdateDetails details) {
|
|
|
|
if (details.delta.dy > 16) {
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
}
|
2020-10-15 18:37:36 +00:00
|
|
|
},
|
2020-10-19 19:28:45 +00:00
|
|
|
child: PageView(
|
|
|
|
controller: _pageController,
|
|
|
|
onPageChanged: (int index) {
|
2021-02-09 20:14:14 +00:00
|
|
|
if (pageViewLock) {
|
|
|
|
pageViewLock = false;
|
|
|
|
return;
|
|
|
|
}
|
2020-10-19 19:28:45 +00:00
|
|
|
if (_animationLock) return;
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.skipToQueueItem(index);
|
2020-10-19 19:28:45 +00:00
|
|
|
},
|
2021-08-29 22:25:18 +00:00
|
|
|
children: List.generate(
|
2021-09-01 12:38:32 +00:00
|
|
|
audioHandler.queue.value.length,
|
2021-08-29 22:25:18 +00:00
|
|
|
(i) => ZoomableImage(
|
2021-09-01 12:38:32 +00:00
|
|
|
url: audioHandler.queue.value[i].artUri.toString(),
|
2021-08-29 22:25:18 +00:00
|
|
|
)),
|
2020-10-19 19:28:45 +00:00
|
|
|
),
|
2020-10-15 18:37:36 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-07-18 21:45:48 +00:00
|
|
|
|
2020-06-23 19:23:12 +00:00
|
|
|
//Top row containing QueueSource, queue...
|
|
|
|
class PlayerScreenTopRow extends StatelessWidget {
|
2021-09-01 12:38:32 +00:00
|
|
|
final double? textSize;
|
|
|
|
final double? iconSize;
|
|
|
|
final double? textWidth;
|
|
|
|
final bool? short;
|
2021-04-05 20:27:54 +00:00
|
|
|
PlayerScreenTopRow(
|
|
|
|
{this.textSize, this.iconSize, this.textWidth, this.short});
|
2020-07-18 21:45:48 +00:00
|
|
|
|
2020-06-23 19:23:12 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
2020-10-19 19:28:45 +00:00
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
2020-06-23 19:23:12 +00:00
|
|
|
children: <Widget>[
|
2020-10-19 19:28:45 +00:00
|
|
|
Container(
|
2021-04-05 20:27:54 +00:00
|
|
|
width: this.textWidth ?? ScreenUtil().setWidth(800),
|
2020-10-19 19:28:45 +00:00
|
|
|
child: Text(
|
2021-04-05 20:27:54 +00:00
|
|
|
(short ?? false)
|
2021-09-01 12:38:32 +00:00
|
|
|
? (playerHelper.queueSource!.text ?? '')
|
2021-04-05 20:27:54 +00:00
|
|
|
: 'Playing from:'.i18n +
|
|
|
|
' ' +
|
|
|
|
(playerHelper.queueSource?.text ?? ''),
|
2020-10-19 19:28:45 +00:00
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
textAlign: TextAlign.left,
|
2021-04-05 20:27:54 +00:00
|
|
|
style: TextStyle(fontSize: this.textSize ?? ScreenUtil().setSp(38)),
|
2020-10-19 19:28:45 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
IconButton(
|
2021-07-23 17:14:21 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.menu,
|
|
|
|
semanticLabel: "Queue".i18n,
|
|
|
|
),
|
2021-04-05 20:27:54 +00:00
|
|
|
iconSize: this.iconSize ?? ScreenUtil().setSp(52),
|
|
|
|
splashRadius: this.iconSize ?? ScreenUtil().setWidth(52),
|
2021-08-29 22:25:18 +00:00
|
|
|
onPressed: () => Navigator.of(context)
|
|
|
|
.push(MaterialPageRoute(builder: (context) => QueueScreen())),
|
2020-06-23 19:23:12 +00:00
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SeekBar extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_SeekBarState createState() => _SeekBarState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SeekBarState extends State<SeekBar> {
|
2021-09-01 12:38:32 +00:00
|
|
|
bool _seeking = false;
|
|
|
|
late StreamSubscription _subscription;
|
|
|
|
final position = ValueNotifier<Duration>(Duration.zero);
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
_subscription = AudioService.position.listen((position) {
|
|
|
|
if (_seeking) return; // user is seeking
|
|
|
|
this.position.value = position;
|
|
|
|
});
|
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_subscription.cancel();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
double parseDuration(Duration position) {
|
|
|
|
if (position > duration) return duration.inMilliseconds.toDouble();
|
|
|
|
return position.inMilliseconds.toDouble();
|
2020-06-23 19:23:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Duration to mm:ss
|
2021-09-01 12:38:32 +00:00
|
|
|
String _timeString(Duration d) {
|
2020-06-23 19:23:12 +00:00
|
|
|
return "${d.inMinutes}:${d.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
|
|
|
}
|
|
|
|
|
2021-09-01 12:38:32 +00:00
|
|
|
Duration get duration {
|
|
|
|
if (audioHandler.mediaItem.value == null) return Duration.zero;
|
|
|
|
return audioHandler.mediaItem.value!.duration!;
|
2020-06-23 19:23:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-09-01 12:38:32 +00:00
|
|
|
return Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 24.0),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
|
|
|
ValueListenableBuilder<Duration>(
|
|
|
|
valueListenable: position,
|
|
|
|
builder: (context, value, _) => Text(
|
|
|
|
_timeString(value),
|
|
|
|
style: TextStyle(fontSize: ScreenUtil().setSp(35)),
|
|
|
|
)),
|
|
|
|
StreamBuilder<MediaItem?>(
|
|
|
|
stream: audioHandler.mediaItem,
|
|
|
|
builder: (context, snapshot) => Text(
|
|
|
|
_timeString(snapshot.data?.duration ?? Duration.zero),
|
|
|
|
style: TextStyle(fontSize: ScreenUtil().setSp(35)),
|
|
|
|
)),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
ValueListenableBuilder<Duration>(
|
|
|
|
valueListenable: position,
|
|
|
|
builder: (context, value, _) => Slider(
|
|
|
|
focusNode: FocusNode(
|
|
|
|
canRequestFocus: false,
|
|
|
|
skipTraversal:
|
|
|
|
true), // Don't focus on Slider - it doesn't work (and not needed)
|
|
|
|
value: parseDuration(value),
|
|
|
|
max: duration.inMilliseconds.toDouble(),
|
|
|
|
onChangeStart: (double d) {
|
|
|
|
_seeking = true;
|
|
|
|
position.value = Duration(milliseconds: d.toInt());
|
|
|
|
},
|
|
|
|
onChanged: (double d) {
|
|
|
|
position.value = Duration(milliseconds: d.toInt());
|
|
|
|
},
|
|
|
|
onChangeEnd: (double d) {
|
|
|
|
_seeking = false;
|
|
|
|
audioHandler.seek(Duration(milliseconds: d.toInt()));
|
|
|
|
},
|
|
|
|
)),
|
|
|
|
],
|
2020-06-23 19:23:12 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class QueueScreen extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_QueueScreenState createState() => _QueueScreenState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _QueueScreenState extends State<QueueScreen> {
|
2021-09-01 12:38:32 +00:00
|
|
|
late StreamSubscription _queueSub;
|
2020-10-17 19:20:26 +00:00
|
|
|
|
2021-09-01 12:38:32 +00:00
|
|
|
/// Basically a simple list that keeps itself synchronized with [AudioHandler.queue],
|
2021-08-29 22:25:18 +00:00
|
|
|
/// so that the [ReorderableListView] is updated instanly (as it should be)
|
|
|
|
List<MediaItem> _queueCache = [];
|
|
|
|
|
2020-10-17 19:20:26 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
2021-09-01 12:38:32 +00:00
|
|
|
_queueCache = audioHandler.queue.value;
|
|
|
|
_queueSub = audioHandler.queue.listen((newQueue) {
|
|
|
|
print('got queue $newQueue');
|
|
|
|
// avoid rebuilding if the cache has got the right update
|
|
|
|
if (listEquals(_queueCache, newQueue)) return;
|
|
|
|
setState(() => _queueCache = newQueue);
|
2021-04-05 20:27:54 +00:00
|
|
|
});
|
2020-10-17 19:20:26 +00:00
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-09-01 12:38:32 +00:00
|
|
|
_queueSub.cancel();
|
2020-10-17 19:20:26 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2020-06-23 19:23:12 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
2020-10-19 19:28:45 +00:00
|
|
|
appBar: FreezerAppBar(
|
|
|
|
'Queue'.i18n,
|
2020-06-23 19:23:12 +00:00
|
|
|
actions: <Widget>[
|
|
|
|
IconButton(
|
2020-08-13 17:39:22 +00:00
|
|
|
icon: Icon(
|
|
|
|
Icons.shuffle,
|
2021-07-02 16:28:59 +00:00
|
|
|
semanticLabel: "Shuffle".i18n,
|
2020-08-13 17:39:22 +00:00
|
|
|
),
|
2020-06-23 19:23:12 +00:00
|
|
|
onPressed: () async {
|
2020-08-13 17:39:22 +00:00
|
|
|
await playerHelper.toggleShuffle();
|
|
|
|
setState(() {});
|
2020-06-23 19:23:12 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
2021-04-05 20:27:54 +00:00
|
|
|
body: ReorderableListView.builder(
|
|
|
|
onReorder: (int oldIndex, int newIndex) {
|
2020-10-16 18:54:04 +00:00
|
|
|
if (oldIndex == playerHelper.queueIndex) return;
|
2021-08-29 22:25:18 +00:00
|
|
|
setState(() => _queueCache.reorder(oldIndex, newIndex));
|
|
|
|
playerHelper.reorder(oldIndex, newIndex);
|
2020-10-16 18:54:04 +00:00
|
|
|
},
|
2021-08-29 22:25:18 +00:00
|
|
|
itemCount: _queueCache.length,
|
2021-04-05 20:27:54 +00:00
|
|
|
itemBuilder: (BuildContext context, int i) {
|
2021-09-01 12:38:32 +00:00
|
|
|
Track track = Track.fromMediaItem(audioHandler.queue.value[i]);
|
|
|
|
return Dismissible(
|
2021-08-29 22:25:18 +00:00
|
|
|
key: Key(track.id),
|
2021-09-01 12:38:32 +00:00
|
|
|
background: DecoratedBox(
|
|
|
|
decoration: BoxDecoration(color: Colors.red),
|
|
|
|
child: Align(
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
|
|
child: Icon(Icons.delete)),
|
|
|
|
alignment: Alignment.centerLeft)),
|
|
|
|
secondaryBackground: DecoratedBox(
|
|
|
|
decoration: BoxDecoration(color: Colors.red),
|
|
|
|
child: Align(
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
|
|
child: Icon(Icons.delete)),
|
|
|
|
alignment: Alignment.centerRight)),
|
|
|
|
onDismissed: (_) {
|
|
|
|
audioHandler.removeQueueItemAt(i);
|
|
|
|
setState(() => _queueCache.removeAt(i));
|
|
|
|
},
|
|
|
|
child: TrackTile(
|
|
|
|
track,
|
|
|
|
onTap: () {
|
|
|
|
pageViewLock = true;
|
|
|
|
audioHandler
|
|
|
|
.skipToQueueItem(i)
|
|
|
|
.then((value) => Navigator.of(context).pop());
|
2020-12-14 17:29:28 +00:00
|
|
|
},
|
2021-09-01 12:38:32 +00:00
|
|
|
key: Key(track.id),
|
2020-12-14 17:29:28 +00:00
|
|
|
),
|
2020-06-23 19:23:12 +00:00
|
|
|
);
|
2021-04-05 20:27:54 +00:00
|
|
|
},
|
|
|
|
));
|
2020-06-23 19:23:12 +00:00
|
|
|
}
|
2021-04-05 20:27:54 +00:00
|
|
|
}
|