lyrics: allow user to scroll freely

This commit is contained in:
pato05 2021-04-05 22:27:54 +02:00
parent e30e2bebf1
commit 122c7c2ae7
2 changed files with 255 additions and 257 deletions

View file

@ -24,14 +24,15 @@ class _LyricsScreenState extends State<LyricsScreen> {
Lyrics lyrics; Lyrics lyrics;
bool _loading = true; bool _loading = true;
bool _error = false; bool _error = false;
int _currentIndex = 0; int _currentIndex = -1;
int _prevIndex = 0; int _prevIndex = -1;
Timer _timer; Timer _timer;
ScrollController _controller = ScrollController(); ScrollController _controller = ScrollController();
StreamSubscription _mediaItemSub; StreamSubscription _mediaItemSub;
final double height = 90; final double height = 90;
bool _freeScroll = false; bool _freeScroll = false;
bool _animatedScroll = false;
Future _load() async { Future _load() async {
//Already available //Already available
@ -59,15 +60,17 @@ class _LyricsScreenState extends State<LyricsScreen> {
} }
} }
void _scrollToLyric() { Future<void> _scrollToLyric() async {
//Lyric height, screen height, appbar height //Lyric height, screen height, appbar height
double _scrollTo = (height * _currentIndex) - double _scrollTo = (height * _currentIndex) -
(MediaQuery.of(context).size.height / 2) + (MediaQuery.of(context).size.height / 2) +
(height / 2) + (height / 2) +
56; 56;
if (0 > _scrollTo) return; if (0 > _scrollTo) return;
_controller.animateTo(_scrollTo, _animatedScroll = true;
await _controller.animateTo(_scrollTo,
duration: Duration(milliseconds: 250), curve: Curves.ease); duration: Duration(milliseconds: 250), curve: Curves.ease);
_animatedScroll = false;
} }
@override @override
@ -82,7 +85,7 @@ class _LyricsScreenState extends State<LyricsScreen> {
(l) => l.offset <= AudioService.playbackState.currentPosition); (l) => l.offset <= AudioService.playbackState.currentPosition);
if (_loading) return; if (_loading) return;
//Scroll to current lyric //Scroll to current lyric
if (_currentIndex <= 0) return; if (_currentIndex < 0) return;
if (_prevIndex == _currentIndex) return; if (_prevIndex == _currentIndex) return;
//Update current lyric index //Update current lyric index
setState(() => null); setState(() => null);
@ -149,8 +152,10 @@ class _LyricsScreenState extends State<LyricsScreen> {
children: [ children: [
//Visualizer //Visualizer
if (settings.lyricsVisualizer) if (settings.lyricsVisualizer)
Align( Positioned(
alignment: Alignment.bottomCenter, bottom: 0,
left: 0,
right: 0,
child: StreamBuilder( child: StreamBuilder(
stream: playerHelper.visualizerStream, stream: playerHelper.visualizerStream,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
@ -193,17 +198,10 @@ class _LyricsScreenState extends State<LyricsScreen> {
) )
: NotificationListener( : NotificationListener(
onNotification: (Notification notification) { onNotification: (Notification notification) {
if (notification is! ScrollEndNotification) if (_freeScroll ||
notification is! ScrollStartNotification)
return false; return false;
if (_freeScroll) return false; if (!_animatedScroll)
double _currentScroll = _controller.position.pixels;
double _expectedScroll = (height * _currentIndex) -
(MediaQuery.of(context).size.height / 2) +
(height / 2) +
56;
print(
'current: $_currentScroll, expected: $_expectedScroll');
if (_currentScroll != _expectedScroll)
setState(() => _freeScroll = true); setState(() => _freeScroll = true);
return false; return false;
}, },

View file

@ -40,7 +40,6 @@ class PlayerScreen extends StatefulWidget {
} }
class _PlayerScreenState extends State<PlayerScreen> { class _PlayerScreenState extends State<PlayerScreen> {
LinearGradient _bgGradient; LinearGradient _bgGradient;
StreamSubscription _mediaItemSub; StreamSubscription _mediaItemSub;
ImageProvider _blurImage; ImageProvider _blurImage;
@ -53,35 +52,51 @@ class _PlayerScreenState extends State<PlayerScreen> {
//BG Image //BG Image
if (settings.blurPlayerBackground) if (settings.blurPlayerBackground)
setState(() { setState(() {
_blurImage = NetworkImage(AudioService.currentMediaItem.extras['thumb'] ?? AudioService.currentMediaItem.artUri); _blurImage = NetworkImage(
AudioService.currentMediaItem.extras['thumb'] ??
AudioService.currentMediaItem.artUri);
}); });
//Run in isolate //Run in isolate
PaletteGenerator palette = await PaletteGenerator.fromImageProvider(CachedNetworkImageProvider(AudioService.currentMediaItem.extras['thumb'] ?? AudioService.currentMediaItem.artUri)); PaletteGenerator palette = await PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(
AudioService.currentMediaItem.extras['thumb'] ??
AudioService.currentMediaItem.artUri));
//Update notification //Update notification
if (settings.blurPlayerBackground) if (settings.blurPlayerBackground)
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor.color.withOpacity(0.25), statusBarColor: palette.dominantColor.color.withOpacity(0.25),
systemNavigationBarColor: Color.alphaBlend(palette.dominantColor.color.withOpacity(0.25), Theme.of(context).scaffoldBackgroundColor), systemNavigationBarColor: Color.alphaBlend(
systemNavigationBarIconBrightness: ThemeData.estimateBrightnessForColor(palette.dominantColor.color) == Brightness.light ? Brightness.dark : Brightness.light palette.dominantColor.color.withOpacity(0.25),
)); Theme.of(context).scaffoldBackgroundColor),
systemNavigationBarIconBrightness:
ThemeData.estimateBrightnessForColor(
palette.dominantColor.color) ==
Brightness.light
? Brightness.dark
: Brightness.light));
//Color gradient //Color gradient
if (!settings.blurPlayerBackground) { if (!settings.blurPlayerBackground) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor.color.withOpacity(0.7), statusBarColor: palette.dominantColor.color.withOpacity(0.7),
statusBarIconBrightness: ThemeData.estimateBrightnessForColor(palette.dominantColor.color.withOpacity(0.7)) == Brightness.light ? Brightness.dark : Brightness.light statusBarIconBrightness: ThemeData.estimateBrightnessForColor(
)); palette.dominantColor.color.withOpacity(0.7)) ==
Brightness.light
? Brightness.dark
: Brightness.light));
setState(() => _bgGradient = LinearGradient( setState(() => _bgGradient = LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [palette.dominantColor.color.withOpacity(0.7), Color.fromARGB(0, 0, 0, 0)], colors: [
stops: [ palette.dominantColor.color.withOpacity(0.7),
0.0, Color.fromARGB(0, 0, 0, 0)
0.6 ],
] stops: [
)); 0.0,
0.6
]));
} }
} }
@ -98,13 +113,11 @@ class _PlayerScreenState extends State<PlayerScreen> {
@override @override
void dispose() { void dispose() {
if (_mediaItemSub != null) if (_mediaItemSub != null) _mediaItemSub.cancel();
_mediaItemSub.cancel();
//Fix bottom buttons //Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarColor, systemNavigationBarColor: settings.themeData.bottomAppBarColor,
statusBarColor: Colors.transparent statusBarColor: Colors.transparent));
));
super.dispose(); super.dispose();
} }
@ -114,57 +127,57 @@ class _PlayerScreenState extends State<PlayerScreen> {
ScreenUtil.init(context, allowFontScaling: true); ScreenUtil.init(context, allowFontScaling: true);
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: settings.blurPlayerBackground ? null : _bgGradient gradient:
), settings.blurPlayerBackground ? null : _bgGradient),
child: Stack( child: Stack(
children: [ children: [
if (settings.blurPlayerBackground && _blurImage != null) if (settings.blurPlayerBackground && _blurImage != null)
ClipRect( ClipRect(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: _blurImage, image: _blurImage,
fit: BoxFit.fill, fit: BoxFit.fill,
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.25), BlendMode.dstATop) colorFilter: ColorFilter.mode(
) Colors.black.withOpacity(0.25),
BlendMode.dstATop))),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(color: Colors.transparent),
),
),
),
StreamBuilder(
stream: StreamZip([
AudioService.playbackStateStream,
AudioService.currentMediaItemStream
]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//When disconnected
if (AudioService.currentMediaItem == null) {
playerHelper.startService();
return Center(
child: CircularProgressIndicator(),
);
}
return OrientationBuilder(
builder: (context, orientation) {
//Landscape
if (orientation == Orientation.landscape) {
return PlayerScreenHorizontal();
}
//Portrait
return PlayerScreenVertical();
},
);
},
), ),
child: BackdropFilter( ],
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), ))));
child: Container(color: Colors.transparent),
),
),
),
StreamBuilder(
stream: StreamZip([AudioService.playbackStateStream, AudioService.currentMediaItemStream]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//When disconnected
if (AudioService.currentMediaItem == null) {
playerHelper.startService();
return Center(child: CircularProgressIndicator(),);
}
return OrientationBuilder(
builder: (context, orientation) {
//Landscape
if (orientation == Orientation.landscape) {
return PlayerScreenHorizontal();
}
//Portrait
return PlayerScreenVertical();
},
);
},
),
],
)
)
)
);
} }
} }
@ -184,13 +197,13 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
Padding( Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 8), padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
child: Container( child: Container(
width: ScreenUtil().setWidth(500), width: ScreenUtil().setWidth(500),
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
BigAlbumArt(), BigAlbumArt(),
], ],
),
), ),
),
), ),
//Right side //Right side
SizedBox( SizedBox(
@ -203,41 +216,40 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
padding: EdgeInsets.fromLTRB(8, 16, 8, 0), padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
child: Container( child: Container(
child: PlayerScreenTopRow( child: PlayerScreenTopRow(
textSize: ScreenUtil().setSp(24), textSize: ScreenUtil().setSp(24),
iconSize: ScreenUtil().setSp(36), iconSize: ScreenUtil().setSp(36),
textWidth: ScreenUtil().setWidth(350), textWidth: ScreenUtil().setWidth(350),
short: true short: true),
), )),
)
),
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Container(
height: ScreenUtil().setSp(50), height: ScreenUtil().setSp(50),
child: AudioService.currentMediaItem.displayTitle.length >= 22 ? child: AudioService
Marquee( .currentMediaItem.displayTitle.length >=
text: AudioService.currentMediaItem.displayTitle, 22
style: TextStyle( ? Marquee(
fontSize: ScreenUtil().setSp(40), text: AudioService.currentMediaItem.displayTitle,
fontWeight: FontWeight.bold style: TextStyle(
), fontSize: ScreenUtil().setSp(40),
blankSpace: 32.0, fontWeight: FontWeight.bold),
startPadding: 10.0, blankSpace: 32.0,
accelerationDuration: Duration(seconds: 1), startPadding: 10.0,
pauseAfterRound: Duration(seconds: 2), accelerationDuration: Duration(seconds: 1),
): pauseAfterRound: Duration(seconds: 2),
Text( )
AudioService.currentMediaItem.displayTitle, : Text(
maxLines: 1, AudioService.currentMediaItem.displayTitle,
overflow: TextOverflow.ellipsis, maxLines: 1,
style: TextStyle( overflow: TextOverflow.ellipsis,
fontSize: ScreenUtil().setSp(40), style: TextStyle(
fontWeight: FontWeight.bold fontSize: ScreenUtil().setSp(40),
), fontWeight: FontWeight.bold),
) )),
Container(
height: 4,
), ),
Container(height: 4,),
Text( Text(
AudioService.currentMediaItem.displaySubtitle ?? '', AudioService.currentMediaItem.displaySubtitle ?? '',
maxLines: 1, maxLines: 1,
@ -264,11 +276,13 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(32)), icon: Icon(Icons.subtitles,
size: ScreenUtil().setWidth(32)),
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) => LyricsScreen(trackId: AudioService.currentMediaItem.id) builder: (context) => LyricsScreen(
)); trackId:
AudioService.currentMediaItem.id)));
}, },
), ),
QualityInfoWidget(), QualityInfoWidget(),
@ -276,8 +290,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
PlayerMenuButton() PlayerMenuButton()
], ],
), ),
) ))
)
], ],
), ),
) )
@ -286,8 +299,6 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
} }
} }
//Portrait //Portrait
class PlayerScreenVertical extends StatefulWidget { class PlayerScreenVertical extends StatefulWidget {
@override @override
@ -302,9 +313,8 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.fromLTRB(30, 4, 16, 0), padding: EdgeInsets.fromLTRB(30, 4, 16, 0),
child: PlayerScreenTopRow() child: PlayerScreenTopRow()),
),
Padding( Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 0), padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: Container( child: Container(
@ -321,30 +331,29 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Container(
height: ScreenUtil().setSp(80), height: ScreenUtil().setSp(80),
child: AudioService.currentMediaItem.displayTitle.length >= 26 ? child: AudioService.currentMediaItem.displayTitle.length >= 26
Marquee( ? Marquee(
text: AudioService.currentMediaItem.displayTitle, text: AudioService.currentMediaItem.displayTitle,
style: TextStyle( style: TextStyle(
fontSize: ScreenUtil().setSp(64), fontSize: ScreenUtil().setSp(64),
fontWeight: FontWeight.bold fontWeight: FontWeight.bold),
), blankSpace: 32.0,
blankSpace: 32.0, startPadding: 10.0,
startPadding: 10.0, accelerationDuration: Duration(seconds: 1),
accelerationDuration: Duration(seconds: 1), pauseAfterRound: Duration(seconds: 2),
pauseAfterRound: Duration(seconds: 2), )
): : Text(
Text( AudioService.currentMediaItem.displayTitle,
AudioService.currentMediaItem.displayTitle, maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, style: TextStyle(
style: TextStyle( fontSize: ScreenUtil().setSp(64),
fontSize: ScreenUtil().setSp(64), fontWeight: FontWeight.bold),
fontWeight: FontWeight.bold )),
), Container(
) height: 4,
), ),
Container(height: 4,),
Text( Text(
AudioService.currentMediaItem.displaySubtitle ?? '', AudioService.currentMediaItem.displaySubtitle ?? '',
maxLines: 1, maxLines: 1,
@ -370,13 +379,13 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
onPressed: () async { onPressed: () async {
//Fix bottom buttons //Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarColor, systemNavigationBarColor:
statusBarColor: Colors.transparent settings.themeData.bottomAppBarColor,
)); statusBarColor: Colors.transparent));
await Navigator.of(context).push(MaterialPageRoute( await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => LyricsScreen(trackId: AudioService.currentMediaItem.id) builder: (context) => LyricsScreen(
)); trackId: AudioService.currentMediaItem.id)));
updateColor(); updateColor();
}, },
@ -398,14 +407,14 @@ class QualityInfoWidget extends StatefulWidget {
} }
class _QualityInfoWidgetState extends State<QualityInfoWidget> { class _QualityInfoWidgetState extends State<QualityInfoWidget> {
String value = ''; String value = '';
StreamSubscription streamSubscription; StreamSubscription streamSubscription;
//Load data from native //Load data from native
void _load() async { void _load() async {
if (AudioService.currentMediaItem == null) return; if (AudioService.currentMediaItem == null) return;
Map data = await DownloadManager.platform.invokeMethod("getStreamInfo", {"id": AudioService.currentMediaItem.id}); Map data = await DownloadManager.platform.invokeMethod(
"getStreamInfo", {"id": AudioService.currentMediaItem.id});
//N/A //N/A
if (data == null) { if (data == null) {
setState(() => value = ''); setState(() => value = '');
@ -418,7 +427,8 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
//Update //Update
StreamQualityInfo info = StreamQualityInfo.fromJson(data); StreamQualityInfo info = StreamQualityInfo.fromJson(data);
setState(() { setState(() {
value = '${info.format} ${info.bitrate(AudioService.currentMediaItem.duration)}kbps'; value =
'${info.format} ${info.bitrate(AudioService.currentMediaItem.duration)}kbps';
}); });
} }
@ -426,7 +436,8 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
void initState() { void initState() {
_load(); _load();
if (streamSubscription == null) if (streamSubscription == null)
streamSubscription = AudioService.currentMediaItemStream.listen((event) async { streamSubscription =
AudioService.currentMediaItemStream.listen((event) async {
_load(); _load();
}); });
super.initState(); super.initState();
@ -434,8 +445,7 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
@override @override
void dispose() { void dispose() {
if (streamSubscription != null) if (streamSubscription != null) streamSubscription.cancel();
streamSubscription.cancel();
super.dispose(); super.dispose();
} }
@ -444,13 +454,13 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
return TextButton( return TextButton(
child: Text(value), child: Text(value),
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => QualitySettings())); Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => QualitySettings()));
}, },
); );
} }
} }
class PlayerMenuButton extends StatelessWidget { class PlayerMenuButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -465,50 +475,37 @@ class PlayerMenuButton extends StatelessWidget {
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]); m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
else else
m.defaultShowEpisodeMenu( m.defaultShowEpisodeMenu(
Show.fromJson(jsonDecode(AudioService.currentMediaItem.extras['show'])), Show.fromJson(
ShowEpisode.fromMediaItem(AudioService.currentMediaItem), jsonDecode(AudioService.currentMediaItem.extras['show'])),
options: [m.sleepTimer(), m.wakelock()] ShowEpisode.fromMediaItem(AudioService.currentMediaItem),
); options: [m.sleepTimer(), m.wakelock()]);
}, },
); );
} }
} }
class RepeatButton extends StatefulWidget { class RepeatButton extends StatefulWidget {
final double iconSize; final double iconSize;
RepeatButton(this.iconSize, {Key key}): super(key: key); RepeatButton(this.iconSize, {Key key}) : super(key: key);
@override @override
_RepeatButtonState createState() => _RepeatButtonState(); _RepeatButtonState createState() => _RepeatButtonState();
} }
class _RepeatButtonState extends State<RepeatButton> { class _RepeatButtonState extends State<RepeatButton> {
Icon get repeatIcon { Icon get repeatIcon {
switch (playerHelper.repeatType) { switch (playerHelper.repeatType) {
case LoopMode.off: case LoopMode.off:
return Icon( return Icon(Icons.repeat, size: widget.iconSize);
Icons.repeat,
size: widget.iconSize
);
case LoopMode.all: case LoopMode.all:
return Icon( return Icon(Icons.repeat,
Icons.repeat, color: Theme.of(context).primaryColor, size: widget.iconSize);
color: Theme.of(context).primaryColor,
size: widget.iconSize
);
case LoopMode.one: case LoopMode.one:
return Icon( return Icon(Icons.repeat_one,
Icons.repeat_one, color: Theme.of(context).primaryColor, size: widget.iconSize);
color: Theme.of(context).primaryColor,
size: widget.iconSize
);
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IconButton( return IconButton(
@ -521,20 +518,18 @@ class _RepeatButtonState extends State<RepeatButton> {
} }
} }
class PlaybackControls extends StatefulWidget { class PlaybackControls extends StatefulWidget {
final double iconSize; final double iconSize;
PlaybackControls(this.iconSize, {Key key}): super(key: key); PlaybackControls(this.iconSize, {Key key}) : super(key: key);
@override @override
_PlaybackControlsState createState() => _PlaybackControlsState(); _PlaybackControlsState createState() => _PlaybackControlsState();
} }
class _PlaybackControlsState extends State<PlaybackControls> { class _PlaybackControlsState extends State<PlaybackControls> {
Icon get libraryIcon { Icon get libraryIcon {
if (cache.checkTrackFavorite(Track.fromMediaItem(AudioService.currentMediaItem))) { if (cache.checkTrackFavorite(
Track.fromMediaItem(AudioService.currentMediaItem))) {
return Icon(Icons.favorite, size: widget.iconSize * 0.64); return Icon(Icons.favorite, size: widget.iconSize * 0.64);
} }
return Icon(Icons.favorite_border, size: widget.iconSize * 0.64); return Icon(Icons.favorite_border, size: widget.iconSize * 0.64);
@ -549,32 +544,37 @@ class _PlaybackControlsState extends State<PlaybackControls> {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.sentiment_very_dissatisfied, size: ScreenUtil().setWidth(46)), icon: Icon(Icons.sentiment_very_dissatisfied,
size: ScreenUtil().setWidth(46)),
onPressed: () async { onPressed: () async {
await deezerAPI.dislikeTrack(AudioService.currentMediaItem.id); await deezerAPI.dislikeTrack(AudioService.currentMediaItem.id);
if (playerHelper.queueIndex < (AudioService.queue??[]).length - 1) { if (playerHelper.queueIndex <
(AudioService.queue ?? []).length - 1) {
AudioService.skipToNext(); AudioService.skipToNext();
} }
} }),
),
PrevNextButton(widget.iconSize, prev: true), PrevNextButton(widget.iconSize, prev: true),
PlayPauseButton(widget.iconSize * 1.25), PlayPauseButton(widget.iconSize * 1.25),
PrevNextButton(widget.iconSize), PrevNextButton(widget.iconSize),
IconButton( IconButton(
icon: libraryIcon, icon: libraryIcon,
onPressed: () async { onPressed: () async {
if (cache.libraryTracks == null) if (cache.libraryTracks == null) cache.libraryTracks = [];
cache.libraryTracks = [];
if (cache.checkTrackFavorite(Track.fromMediaItem(AudioService.currentMediaItem))) { if (cache.checkTrackFavorite(
Track.fromMediaItem(AudioService.currentMediaItem))) {
//Remove from library //Remove from library
setState(() => cache.libraryTracks.remove(AudioService.currentMediaItem.id)); setState(() => cache.libraryTracks
await deezerAPI.removeFavorite(AudioService.currentMediaItem.id); .remove(AudioService.currentMediaItem.id));
await deezerAPI
.removeFavorite(AudioService.currentMediaItem.id);
await cache.save(); await cache.save();
} else { } else {
//Add //Add
setState(() => cache.libraryTracks.add(AudioService.currentMediaItem.id)); setState(() =>
await deezerAPI.addFavoriteTrack(AudioService.currentMediaItem.id); cache.libraryTracks.add(AudioService.currentMediaItem.id));
await deezerAPI
.addFavoriteTrack(AudioService.currentMediaItem.id);
await cache.save(); await cache.save();
} }
}, },
@ -585,14 +585,12 @@ class _PlaybackControlsState extends State<PlaybackControls> {
} }
} }
class BigAlbumArt extends StatefulWidget { class BigAlbumArt extends StatefulWidget {
@override @override
_BigAlbumArtState createState() => _BigAlbumArtState(); _BigAlbumArtState createState() => _BigAlbumArtState();
} }
class _BigAlbumArtState extends State<BigAlbumArt> { class _BigAlbumArtState extends State<BigAlbumArt> {
PageController _pageController = PageController( PageController _pageController = PageController(
initialPage: playerHelper.queueIndex, initialPage: playerHelper.queueIndex,
); );
@ -603,7 +601,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
void initState() { void initState() {
_currentItemSub = AudioService.currentMediaItemStream.listen((event) async { _currentItemSub = AudioService.currentMediaItemStream.listen((event) async {
_animationLock = true; _animationLock = true;
await _pageController.animateToPage(playerHelper.queueIndex, duration: Duration(milliseconds: 300), curve: Curves.easeInOut); await _pageController.animateToPage(playerHelper.queueIndex,
duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
_animationLock = false; _animationLock = false;
}); });
super.initState(); super.initState();
@ -611,8 +610,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
@override @override
void dispose() { void dispose() {
if (_currentItemSub != null) if (_currentItemSub != null) _currentItemSub.cancel();
_currentItemSub.cancel();
super.dispose(); super.dispose();
} }
@ -634,7 +632,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
if (_animationLock) return; if (_animationLock) return;
AudioService.skipToQueueItem(AudioService.queue[index].id); AudioService.skipToQueueItem(AudioService.queue[index].id);
}, },
children: List.generate(AudioService.queue.length, (i) => ZoomableImage(url: AudioService.queue[i].artUri)), children: List.generate(AudioService.queue.length,
(i) => ZoomableImage(url: AudioService.queue[i].artUri)),
), ),
); );
} }
@ -642,12 +641,12 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
//Top row containing QueueSource, queue... //Top row containing QueueSource, queue...
class PlayerScreenTopRow extends StatelessWidget { class PlayerScreenTopRow extends StatelessWidget {
final double textSize; final double textSize;
final double iconSize; final double iconSize;
final double textWidth; final double textWidth;
final bool short; final bool short;
PlayerScreenTopRow({this.textSize, this.iconSize, this.textWidth, this.short}); PlayerScreenTopRow(
{this.textSize, this.iconSize, this.textWidth, this.short});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -657,29 +656,31 @@ class PlayerScreenTopRow extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
width: this.textWidth??ScreenUtil().setWidth(800), width: this.textWidth ?? ScreenUtil().setWidth(800),
child: Text( child: Text(
(short??false)?(playerHelper.queueSource.text??''):'Playing from:'.i18n + ' ' + (playerHelper.queueSource?.text??''), (short ?? false)
? (playerHelper.queueSource.text ?? '')
: 'Playing from:'.i18n +
' ' +
(playerHelper.queueSource?.text ?? ''),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: TextStyle(fontSize: this.textSize??ScreenUtil().setSp(38)), style: TextStyle(fontSize: this.textSize ?? ScreenUtil().setSp(38)),
), ),
), ),
IconButton( IconButton(
icon: Icon(Icons.menu), icon: Icon(Icons.menu),
iconSize: this.iconSize??ScreenUtil().setSp(52), iconSize: this.iconSize ?? ScreenUtil().setSp(52),
splashRadius: this.iconSize??ScreenUtil().setWidth(52), splashRadius: this.iconSize ?? ScreenUtil().setWidth(52),
onPressed: () async { onPressed: () async {
//Fix bottom buttons //Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarColor, systemNavigationBarColor: settings.themeData.bottomAppBarColor,
statusBarColor: Colors.transparent statusBarColor: Colors.transparent));
));
//Navigate //Navigate
await Navigator.of(context).push(MaterialPageRoute( await Navigator.of(context)
builder: (context) => QueueScreen() .push(MaterialPageRoute(builder: (context) => QueueScreen()));
));
//Fix colors //Fix colors
updateColor(); updateColor();
}, },
@ -689,22 +690,21 @@ class PlayerScreenTopRow extends StatelessWidget {
} }
} }
class SeekBar extends StatefulWidget { class SeekBar extends StatefulWidget {
@override @override
_SeekBarState createState() => _SeekBarState(); _SeekBarState createState() => _SeekBarState();
} }
class _SeekBarState extends State<SeekBar> { class _SeekBarState extends State<SeekBar> {
bool _seeking = false; bool _seeking = false;
double _pos; double _pos;
double get position { double get position {
if (_seeking) return _pos; if (_seeking) return _pos;
if (AudioService.playbackState == null) return 0.0; if (AudioService.playbackState == null) return 0.0;
double p = AudioService.playbackState.currentPosition.inMilliseconds.toDouble()??0.0; double p =
AudioService.playbackState.currentPosition.inMilliseconds.toDouble() ??
0.0;
if (p > duration) return duration; if (p > duration) return duration;
return p; return p;
} }
@ -735,15 +735,11 @@ class _SeekBarState extends State<SeekBar> {
children: <Widget>[ children: <Widget>[
Text( Text(
_timeString(position), _timeString(position),
style: TextStyle( style: TextStyle(fontSize: ScreenUtil().setSp(35)),
fontSize: ScreenUtil().setSp(35)
),
), ),
Text( Text(
_timeString(duration), _timeString(duration),
style: TextStyle( style: TextStyle(fontSize: ScreenUtil().setSp(35)),
fontSize: ScreenUtil().setSp(35)
),
) )
], ],
), ),
@ -751,7 +747,10 @@ class _SeekBarState extends State<SeekBar> {
Container( Container(
height: 32.0, height: 32.0,
child: Slider( child: Slider(
focusNode: FocusNode(canRequestFocus: false, skipTraversal: true), // Don't focus on Slider - it doesn't work (and not needed) focusNode: FocusNode(
canRequestFocus: false,
skipTraversal:
true), // Don't focus on Slider - it doesn't work (and not needed)
value: position, value: position,
max: duration, max: duration,
onChangeStart: (double d) { onChangeStart: (double d) {
@ -787,19 +786,19 @@ class QueueScreen extends StatefulWidget {
} }
class _QueueScreenState extends State<QueueScreen> { class _QueueScreenState extends State<QueueScreen> {
StreamSubscription _queueSub; StreamSubscription _queueSub;
@override @override
void initState() { void initState() {
_queueSub = AudioService.queueStream.listen((event) {setState((){});}); _queueSub = AudioService.queueStream.listen((event) {
setState(() {});
});
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
if (_queueSub != null) if (_queueSub != null) _queueSub.cancel();
_queueSub.cancel();
super.dispose(); super.dispose();
} }
@ -820,32 +819,33 @@ class _QueueScreenState extends State<QueueScreen> {
) )
], ],
), ),
body: ReorderableListView( body: ReorderableListView.builder(
onReorder: (int oldIndex, int newIndex) async { onReorder: (int oldIndex, int newIndex) {
if (oldIndex == playerHelper.queueIndex) return; if (oldIndex == playerHelper.queueIndex) return;
await playerHelper.reorder(oldIndex, newIndex); playerHelper
setState(() {}); .reorder(oldIndex, newIndex)
.then((value) => setState(() => null));
}, },
children: List.generate(AudioService.queue.length, (int i) { itemCount: AudioService.queue.length,
itemBuilder: (BuildContext context, int i) {
Track t = Track.fromMediaItem(AudioService.queue[i]); Track t = Track.fromMediaItem(AudioService.queue[i]);
return TrackTile( return TrackTile(
t, t,
onTap: () async { onTap: () {
pageViewLock = true; pageViewLock = true;
await AudioService.skipToQueueItem(t.id); AudioService.skipToQueueItem(t.id)
Navigator.of(context).pop(); .then((value) => Navigator.of(context).pop());
}, },
key: Key(i.toString()), key: Key(i.toString()),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () async { onPressed: () {
await AudioService.removeQueueItem(t.toMediaItem()); AudioService.removeQueueItem(t.toMediaItem())
setState(() {}); .then((value) => setState(() => null));
}, },
), ),
); );
}), },
) ));
);
} }
} }