lyrics: allow user to scroll freely
This commit is contained in:
parent
e30e2bebf1
commit
122c7c2ae7
|
|
@ -24,14 +24,15 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
Lyrics lyrics;
|
||||
bool _loading = true;
|
||||
bool _error = false;
|
||||
int _currentIndex = 0;
|
||||
int _prevIndex = 0;
|
||||
int _currentIndex = -1;
|
||||
int _prevIndex = -1;
|
||||
Timer _timer;
|
||||
ScrollController _controller = ScrollController();
|
||||
StreamSubscription _mediaItemSub;
|
||||
final double height = 90;
|
||||
|
||||
bool _freeScroll = false;
|
||||
bool _animatedScroll = false;
|
||||
|
||||
Future _load() async {
|
||||
//Already available
|
||||
|
|
@ -59,15 +60,17 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
void _scrollToLyric() {
|
||||
Future<void> _scrollToLyric() async {
|
||||
//Lyric height, screen height, appbar height
|
||||
double _scrollTo = (height * _currentIndex) -
|
||||
(MediaQuery.of(context).size.height / 2) +
|
||||
(height / 2) +
|
||||
56;
|
||||
if (0 > _scrollTo) return;
|
||||
_controller.animateTo(_scrollTo,
|
||||
_animatedScroll = true;
|
||||
await _controller.animateTo(_scrollTo,
|
||||
duration: Duration(milliseconds: 250), curve: Curves.ease);
|
||||
_animatedScroll = false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -82,7 +85,7 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
(l) => l.offset <= AudioService.playbackState.currentPosition);
|
||||
if (_loading) return;
|
||||
//Scroll to current lyric
|
||||
if (_currentIndex <= 0) return;
|
||||
if (_currentIndex < 0) return;
|
||||
if (_prevIndex == _currentIndex) return;
|
||||
//Update current lyric index
|
||||
setState(() => null);
|
||||
|
|
@ -149,8 +152,10 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
children: [
|
||||
//Visualizer
|
||||
if (settings.lyricsVisualizer)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: StreamBuilder(
|
||||
stream: playerHelper.visualizerStream,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
|
|
@ -193,17 +198,10 @@ class _LyricsScreenState extends State<LyricsScreen> {
|
|||
)
|
||||
: NotificationListener(
|
||||
onNotification: (Notification notification) {
|
||||
if (notification is! ScrollEndNotification)
|
||||
if (_freeScroll ||
|
||||
notification is! ScrollStartNotification)
|
||||
return false;
|
||||
if (_freeScroll) return false;
|
||||
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)
|
||||
if (!_animatedScroll)
|
||||
setState(() => _freeScroll = true);
|
||||
return false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ class PlayerScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _PlayerScreenState extends State<PlayerScreen> {
|
||||
|
||||
LinearGradient _bgGradient;
|
||||
StreamSubscription _mediaItemSub;
|
||||
ImageProvider _blurImage;
|
||||
|
|
@ -53,35 +52,51 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||
//BG Image
|
||||
if (settings.blurPlayerBackground)
|
||||
setState(() {
|
||||
_blurImage = NetworkImage(AudioService.currentMediaItem.extras['thumb'] ?? AudioService.currentMediaItem.artUri);
|
||||
_blurImage = NetworkImage(
|
||||
AudioService.currentMediaItem.extras['thumb'] ??
|
||||
AudioService.currentMediaItem.artUri);
|
||||
});
|
||||
|
||||
//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
|
||||
if (settings.blurPlayerBackground)
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarColor: palette.dominantColor.color.withOpacity(0.25),
|
||||
systemNavigationBarColor: Color.alphaBlend(palette.dominantColor.color.withOpacity(0.25), Theme.of(context).scaffoldBackgroundColor),
|
||||
systemNavigationBarIconBrightness: ThemeData.estimateBrightnessForColor(palette.dominantColor.color) == Brightness.light ? Brightness.dark : Brightness.light
|
||||
));
|
||||
systemNavigationBarColor: Color.alphaBlend(
|
||||
palette.dominantColor.color.withOpacity(0.25),
|
||||
Theme.of(context).scaffoldBackgroundColor),
|
||||
systemNavigationBarIconBrightness:
|
||||
ThemeData.estimateBrightnessForColor(
|
||||
palette.dominantColor.color) ==
|
||||
Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light));
|
||||
|
||||
//Color gradient
|
||||
if (!settings.blurPlayerBackground) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
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(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [palette.dominantColor.color.withOpacity(0.7), Color.fromARGB(0, 0, 0, 0)],
|
||||
stops: [
|
||||
0.0,
|
||||
0.6
|
||||
]
|
||||
));
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
palette.dominantColor.color.withOpacity(0.7),
|
||||
Color.fromARGB(0, 0, 0, 0)
|
||||
],
|
||||
stops: [
|
||||
0.0,
|
||||
0.6
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,13 +113,11 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_mediaItemSub != null)
|
||||
_mediaItemSub.cancel();
|
||||
if (_mediaItemSub != null) _mediaItemSub.cancel();
|
||||
//Fix bottom buttons
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
|
||||
statusBarColor: Colors.transparent
|
||||
));
|
||||
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
|
||||
statusBarColor: Colors.transparent));
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -114,57 +127,57 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||
ScreenUtil.init(context, allowFontScaling: true);
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: settings.blurPlayerBackground ? null : _bgGradient
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (settings.blurPlayerBackground && _blurImage != null)
|
||||
ClipRect(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: _blurImage,
|
||||
fit: BoxFit.fill,
|
||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.25), BlendMode.dstATop)
|
||||
)
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient:
|
||||
settings.blurPlayerBackground ? null : _bgGradient),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (settings.blurPlayerBackground && _blurImage != null)
|
||||
ClipRect(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: _blurImage,
|
||||
fit: BoxFit.fill,
|
||||
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: EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: Container(
|
||||
width: ScreenUtil().setWidth(500),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
BigAlbumArt(),
|
||||
],
|
||||
),
|
||||
width: ScreenUtil().setWidth(500),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
BigAlbumArt(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
//Right side
|
||||
SizedBox(
|
||||
|
|
@ -203,41 +216,40 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||
padding: EdgeInsets.fromLTRB(8, 16, 8, 0),
|
||||
child: Container(
|
||||
child: PlayerScreenTopRow(
|
||||
textSize: ScreenUtil().setSp(24),
|
||||
iconSize: ScreenUtil().setSp(36),
|
||||
textWidth: ScreenUtil().setWidth(350),
|
||||
short: true
|
||||
),
|
||||
)
|
||||
),
|
||||
textSize: ScreenUtil().setSp(24),
|
||||
iconSize: ScreenUtil().setSp(36),
|
||||
textWidth: ScreenUtil().setWidth(350),
|
||||
short: true),
|
||||
)),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: ScreenUtil().setSp(50),
|
||||
child: AudioService.currentMediaItem.displayTitle.length >= 22 ?
|
||||
Marquee(
|
||||
text: AudioService.currentMediaItem.displayTitle,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(40),
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
blankSpace: 32.0,
|
||||
startPadding: 10.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
pauseAfterRound: Duration(seconds: 2),
|
||||
):
|
||||
Text(
|
||||
AudioService.currentMediaItem.displayTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(40),
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
)
|
||||
height: ScreenUtil().setSp(50),
|
||||
child: AudioService
|
||||
.currentMediaItem.displayTitle.length >=
|
||||
22
|
||||
? Marquee(
|
||||
text: AudioService.currentMediaItem.displayTitle,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(40),
|
||||
fontWeight: FontWeight.bold),
|
||||
blankSpace: 32.0,
|
||||
startPadding: 10.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
pauseAfterRound: Duration(seconds: 2),
|
||||
)
|
||||
: Text(
|
||||
AudioService.currentMediaItem.displayTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(40),
|
||||
fontWeight: FontWeight.bold),
|
||||
)),
|
||||
Container(
|
||||
height: 4,
|
||||
),
|
||||
Container(height: 4,),
|
||||
Text(
|
||||
AudioService.currentMediaItem.displaySubtitle ?? '',
|
||||
maxLines: 1,
|
||||
|
|
@ -264,11 +276,13 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(32)),
|
||||
icon: Icon(Icons.subtitles,
|
||||
size: ScreenUtil().setWidth(32)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => LyricsScreen(trackId: AudioService.currentMediaItem.id)
|
||||
));
|
||||
builder: (context) => LyricsScreen(
|
||||
trackId:
|
||||
AudioService.currentMediaItem.id)));
|
||||
},
|
||||
),
|
||||
QualityInfoWidget(),
|
||||
|
|
@ -276,8 +290,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||
PlayerMenuButton()
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
))
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
@ -286,8 +299,6 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Portrait
|
||||
class PlayerScreenVertical extends StatefulWidget {
|
||||
@override
|
||||
|
|
@ -302,9 +313,8 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(30, 4, 16, 0),
|
||||
child: PlayerScreenTopRow()
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(30, 4, 16, 0),
|
||||
child: PlayerScreenTopRow()),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
child: Container(
|
||||
|
|
@ -321,30 +331,29 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: ScreenUtil().setSp(80),
|
||||
child: AudioService.currentMediaItem.displayTitle.length >= 26 ?
|
||||
Marquee(
|
||||
text: AudioService.currentMediaItem.displayTitle,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(64),
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
blankSpace: 32.0,
|
||||
startPadding: 10.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
pauseAfterRound: Duration(seconds: 2),
|
||||
):
|
||||
Text(
|
||||
AudioService.currentMediaItem.displayTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(64),
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
)
|
||||
height: ScreenUtil().setSp(80),
|
||||
child: AudioService.currentMediaItem.displayTitle.length >= 26
|
||||
? Marquee(
|
||||
text: AudioService.currentMediaItem.displayTitle,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(64),
|
||||
fontWeight: FontWeight.bold),
|
||||
blankSpace: 32.0,
|
||||
startPadding: 10.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
pauseAfterRound: Duration(seconds: 2),
|
||||
)
|
||||
: Text(
|
||||
AudioService.currentMediaItem.displayTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(64),
|
||||
fontWeight: FontWeight.bold),
|
||||
)),
|
||||
Container(
|
||||
height: 4,
|
||||
),
|
||||
Container(height: 4,),
|
||||
Text(
|
||||
AudioService.currentMediaItem.displaySubtitle ?? '',
|
||||
maxLines: 1,
|
||||
|
|
@ -370,13 +379,13 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||
onPressed: () async {
|
||||
//Fix bottom buttons
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
|
||||
statusBarColor: Colors.transparent
|
||||
));
|
||||
systemNavigationBarColor:
|
||||
settings.themeData.bottomAppBarColor,
|
||||
statusBarColor: Colors.transparent));
|
||||
|
||||
await Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => LyricsScreen(trackId: AudioService.currentMediaItem.id)
|
||||
));
|
||||
builder: (context) => LyricsScreen(
|
||||
trackId: AudioService.currentMediaItem.id)));
|
||||
|
||||
updateColor();
|
||||
},
|
||||
|
|
@ -398,14 +407,14 @@ class QualityInfoWidget extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _QualityInfoWidgetState extends State<QualityInfoWidget> {
|
||||
|
||||
String value = '';
|
||||
StreamSubscription streamSubscription;
|
||||
|
||||
//Load data from native
|
||||
void _load() async {
|
||||
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
|
||||
if (data == null) {
|
||||
setState(() => value = '');
|
||||
|
|
@ -418,7 +427,8 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
|
|||
//Update
|
||||
StreamQualityInfo info = StreamQualityInfo.fromJson(data);
|
||||
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() {
|
||||
_load();
|
||||
if (streamSubscription == null)
|
||||
streamSubscription = AudioService.currentMediaItemStream.listen((event) async {
|
||||
streamSubscription =
|
||||
AudioService.currentMediaItemStream.listen((event) async {
|
||||
_load();
|
||||
});
|
||||
super.initState();
|
||||
|
|
@ -434,8 +445,7 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
if (streamSubscription != null)
|
||||
streamSubscription.cancel();
|
||||
if (streamSubscription != null) streamSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -444,13 +454,13 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
|
|||
return TextButton(
|
||||
child: Text(value),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => QualitySettings()));
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => QualitySettings()));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PlayerMenuButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -465,50 +475,37 @@ class PlayerMenuButton extends StatelessWidget {
|
|||
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
||||
else
|
||||
m.defaultShowEpisodeMenu(
|
||||
Show.fromJson(jsonDecode(AudioService.currentMediaItem.extras['show'])),
|
||||
ShowEpisode.fromMediaItem(AudioService.currentMediaItem),
|
||||
options: [m.sleepTimer(), m.wakelock()]
|
||||
);
|
||||
Show.fromJson(
|
||||
jsonDecode(AudioService.currentMediaItem.extras['show'])),
|
||||
ShowEpisode.fromMediaItem(AudioService.currentMediaItem),
|
||||
options: [m.sleepTimer(), m.wakelock()]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RepeatButton extends StatefulWidget {
|
||||
|
||||
final double iconSize;
|
||||
RepeatButton(this.iconSize, {Key key}): super(key: key);
|
||||
RepeatButton(this.iconSize, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RepeatButtonState createState() => _RepeatButtonState();
|
||||
}
|
||||
|
||||
class _RepeatButtonState extends State<RepeatButton> {
|
||||
|
||||
Icon get repeatIcon {
|
||||
switch (playerHelper.repeatType) {
|
||||
case LoopMode.off:
|
||||
return Icon(
|
||||
Icons.repeat,
|
||||
size: widget.iconSize
|
||||
);
|
||||
return Icon(Icons.repeat, size: widget.iconSize);
|
||||
case LoopMode.all:
|
||||
return Icon(
|
||||
Icons.repeat,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: widget.iconSize
|
||||
);
|
||||
return Icon(Icons.repeat,
|
||||
color: Theme.of(context).primaryColor, size: widget.iconSize);
|
||||
case LoopMode.one:
|
||||
return Icon(
|
||||
Icons.repeat_one,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: widget.iconSize
|
||||
);
|
||||
return Icon(Icons.repeat_one,
|
||||
color: Theme.of(context).primaryColor, size: widget.iconSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
|
|
@ -521,20 +518,18 @@ class _RepeatButtonState extends State<RepeatButton> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class PlaybackControls extends StatefulWidget {
|
||||
|
||||
final double iconSize;
|
||||
PlaybackControls(this.iconSize, {Key key}): super(key: key);
|
||||
PlaybackControls(this.iconSize, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_PlaybackControlsState createState() => _PlaybackControlsState();
|
||||
}
|
||||
|
||||
class _PlaybackControlsState extends State<PlaybackControls> {
|
||||
|
||||
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_border, size: widget.iconSize * 0.64);
|
||||
|
|
@ -549,32 +544,37 @@ class _PlaybackControlsState extends State<PlaybackControls> {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.sentiment_very_dissatisfied, size: ScreenUtil().setWidth(46)),
|
||||
icon: Icon(Icons.sentiment_very_dissatisfied,
|
||||
size: ScreenUtil().setWidth(46)),
|
||||
onPressed: () async {
|
||||
await deezerAPI.dislikeTrack(AudioService.currentMediaItem.id);
|
||||
if (playerHelper.queueIndex < (AudioService.queue??[]).length - 1) {
|
||||
if (playerHelper.queueIndex <
|
||||
(AudioService.queue ?? []).length - 1) {
|
||||
AudioService.skipToNext();
|
||||
}
|
||||
}
|
||||
),
|
||||
}),
|
||||
PrevNextButton(widget.iconSize, prev: true),
|
||||
PlayPauseButton(widget.iconSize * 1.25),
|
||||
PrevNextButton(widget.iconSize),
|
||||
IconButton(
|
||||
icon: libraryIcon,
|
||||
onPressed: () async {
|
||||
if (cache.libraryTracks == null)
|
||||
cache.libraryTracks = [];
|
||||
if (cache.libraryTracks == null) cache.libraryTracks = [];
|
||||
|
||||
if (cache.checkTrackFavorite(Track.fromMediaItem(AudioService.currentMediaItem))) {
|
||||
if (cache.checkTrackFavorite(
|
||||
Track.fromMediaItem(AudioService.currentMediaItem))) {
|
||||
//Remove from library
|
||||
setState(() => cache.libraryTracks.remove(AudioService.currentMediaItem.id));
|
||||
await deezerAPI.removeFavorite(AudioService.currentMediaItem.id);
|
||||
setState(() => cache.libraryTracks
|
||||
.remove(AudioService.currentMediaItem.id));
|
||||
await deezerAPI
|
||||
.removeFavorite(AudioService.currentMediaItem.id);
|
||||
await cache.save();
|
||||
} else {
|
||||
//Add
|
||||
setState(() => cache.libraryTracks.add(AudioService.currentMediaItem.id));
|
||||
await deezerAPI.addFavoriteTrack(AudioService.currentMediaItem.id);
|
||||
setState(() =>
|
||||
cache.libraryTracks.add(AudioService.currentMediaItem.id));
|
||||
await deezerAPI
|
||||
.addFavoriteTrack(AudioService.currentMediaItem.id);
|
||||
await cache.save();
|
||||
}
|
||||
},
|
||||
|
|
@ -585,14 +585,12 @@ class _PlaybackControlsState extends State<PlaybackControls> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class BigAlbumArt extends StatefulWidget {
|
||||
@override
|
||||
_BigAlbumArtState createState() => _BigAlbumArtState();
|
||||
}
|
||||
|
||||
class _BigAlbumArtState extends State<BigAlbumArt> {
|
||||
|
||||
PageController _pageController = PageController(
|
||||
initialPage: playerHelper.queueIndex,
|
||||
);
|
||||
|
|
@ -603,7 +601,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
|
|||
void initState() {
|
||||
_currentItemSub = AudioService.currentMediaItemStream.listen((event) async {
|
||||
_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;
|
||||
});
|
||||
super.initState();
|
||||
|
|
@ -611,8 +610,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_currentItemSub != null)
|
||||
_currentItemSub.cancel();
|
||||
if (_currentItemSub != null) _currentItemSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -634,7 +632,8 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
|
|||
if (_animationLock) return;
|
||||
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...
|
||||
class PlayerScreenTopRow extends StatelessWidget {
|
||||
|
||||
final double textSize;
|
||||
final double iconSize;
|
||||
final double textWidth;
|
||||
final bool short;
|
||||
PlayerScreenTopRow({this.textSize, this.iconSize, this.textWidth, this.short});
|
||||
PlayerScreenTopRow(
|
||||
{this.textSize, this.iconSize, this.textWidth, this.short});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -657,29 +656,31 @@ class PlayerScreenTopRow extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: this.textWidth??ScreenUtil().setWidth(800),
|
||||
width: this.textWidth ?? ScreenUtil().setWidth(800),
|
||||
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,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(fontSize: this.textSize??ScreenUtil().setSp(38)),
|
||||
style: TextStyle(fontSize: this.textSize ?? ScreenUtil().setSp(38)),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
iconSize: this.iconSize??ScreenUtil().setSp(52),
|
||||
splashRadius: this.iconSize??ScreenUtil().setWidth(52),
|
||||
iconSize: this.iconSize ?? ScreenUtil().setSp(52),
|
||||
splashRadius: this.iconSize ?? ScreenUtil().setWidth(52),
|
||||
onPressed: () async {
|
||||
//Fix bottom buttons
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
|
||||
statusBarColor: Colors.transparent
|
||||
));
|
||||
statusBarColor: Colors.transparent));
|
||||
//Navigate
|
||||
await Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => QueueScreen()
|
||||
));
|
||||
await Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => QueueScreen()));
|
||||
//Fix colors
|
||||
updateColor();
|
||||
},
|
||||
|
|
@ -689,22 +690,21 @@ class PlayerScreenTopRow extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SeekBar extends StatefulWidget {
|
||||
@override
|
||||
_SeekBarState createState() => _SeekBarState();
|
||||
}
|
||||
|
||||
class _SeekBarState extends State<SeekBar> {
|
||||
|
||||
bool _seeking = false;
|
||||
double _pos;
|
||||
|
||||
double get position {
|
||||
if (_seeking) return _pos;
|
||||
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;
|
||||
return p;
|
||||
}
|
||||
|
|
@ -735,15 +735,11 @@ class _SeekBarState extends State<SeekBar> {
|
|||
children: <Widget>[
|
||||
Text(
|
||||
_timeString(position),
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(35)
|
||||
),
|
||||
style: TextStyle(fontSize: ScreenUtil().setSp(35)),
|
||||
),
|
||||
Text(
|
||||
_timeString(duration),
|
||||
style: TextStyle(
|
||||
fontSize: ScreenUtil().setSp(35)
|
||||
),
|
||||
style: TextStyle(fontSize: ScreenUtil().setSp(35)),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -751,7 +747,10 @@ class _SeekBarState extends State<SeekBar> {
|
|||
Container(
|
||||
height: 32.0,
|
||||
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,
|
||||
max: duration,
|
||||
onChangeStart: (double d) {
|
||||
|
|
@ -787,19 +786,19 @@ class QueueScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _QueueScreenState extends State<QueueScreen> {
|
||||
|
||||
StreamSubscription _queueSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_queueSub = AudioService.queueStream.listen((event) {setState((){});});
|
||||
_queueSub = AudioService.queueStream.listen((event) {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_queueSub != null)
|
||||
_queueSub.cancel();
|
||||
if (_queueSub != null) _queueSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -820,32 +819,33 @@ class _QueueScreenState extends State<QueueScreen> {
|
|||
)
|
||||
],
|
||||
),
|
||||
body: ReorderableListView(
|
||||
onReorder: (int oldIndex, int newIndex) async {
|
||||
body: ReorderableListView.builder(
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex == playerHelper.queueIndex) return;
|
||||
await playerHelper.reorder(oldIndex, newIndex);
|
||||
setState(() {});
|
||||
playerHelper
|
||||
.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]);
|
||||
return TrackTile(
|
||||
t,
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
pageViewLock = true;
|
||||
await AudioService.skipToQueueItem(t.id);
|
||||
Navigator.of(context).pop();
|
||||
AudioService.skipToQueueItem(t.id)
|
||||
.then((value) => Navigator.of(context).pop());
|
||||
},
|
||||
key: Key(i.toString()),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () async {
|
||||
await AudioService.removeQueueItem(t.toMediaItem());
|
||||
setState(() {});
|
||||
onPressed: () {
|
||||
AudioService.removeQueueItem(t.toMediaItem())
|
||||
.then((value) => setState(() => null));
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue