2020-11-09 21:05:47 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
import 'package:audio_service/audio_service.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:freezer/api/deezer.dart';
|
|
|
|
import 'package:freezer/api/definitions.dart';
|
2021-02-09 20:14:14 +00:00
|
|
|
import 'package:freezer/api/player.dart';
|
|
|
|
import 'package:freezer/settings.dart';
|
2020-11-09 21:05:47 +00:00
|
|
|
import 'package:freezer/ui/elements.dart';
|
|
|
|
import 'package:freezer/translations.i18n.dart';
|
|
|
|
import 'package:freezer/ui/error.dart';
|
|
|
|
|
|
|
|
class LyricsScreen extends StatefulWidget {
|
|
|
|
final Lyrics lyrics;
|
|
|
|
final String trackId;
|
|
|
|
|
2021-04-05 15:35:51 +00:00
|
|
|
LyricsScreen({this.lyrics, this.trackId, Key key}) : super(key: key);
|
2020-11-09 21:05:47 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
_LyricsScreenState createState() => _LyricsScreenState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _LyricsScreenState extends State<LyricsScreen> {
|
|
|
|
Lyrics lyrics;
|
|
|
|
bool _loading = true;
|
|
|
|
bool _error = false;
|
|
|
|
int _currentIndex = 0;
|
|
|
|
int _prevIndex = 0;
|
|
|
|
Timer _timer;
|
|
|
|
ScrollController _controller = ScrollController();
|
|
|
|
StreamSubscription _mediaItemSub;
|
|
|
|
final double height = 90;
|
|
|
|
|
2021-04-05 15:35:51 +00:00
|
|
|
bool _freeScroll = false;
|
|
|
|
|
2020-11-09 21:05:47 +00:00
|
|
|
Future _load() async {
|
|
|
|
//Already available
|
|
|
|
if (this.lyrics != null) return;
|
2021-04-04 22:59:57 +00:00
|
|
|
if (widget.lyrics?.lyrics != null && widget.lyrics.lyrics.length > 0) {
|
2020-11-09 21:05:47 +00:00
|
|
|
setState(() {
|
|
|
|
lyrics = widget.lyrics;
|
|
|
|
_loading = false;
|
|
|
|
_error = false;
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Fetch
|
|
|
|
try {
|
|
|
|
Lyrics l = await deezerAPI.lyrics(widget.trackId);
|
|
|
|
setState(() {
|
|
|
|
_loading = false;
|
|
|
|
lyrics = l;
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
setState(() {
|
|
|
|
_error = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-05 15:35:51 +00:00
|
|
|
void _scrollToLyric() {
|
|
|
|
//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,
|
|
|
|
duration: Duration(milliseconds: 250), curve: Curves.ease);
|
|
|
|
}
|
|
|
|
|
2020-11-09 21:05:47 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
_load();
|
|
|
|
|
2021-02-09 20:14:14 +00:00
|
|
|
//Enable visualizer
|
2021-04-05 15:35:51 +00:00
|
|
|
if (settings.lyricsVisualizer) playerHelper.startVisualizer();
|
2020-11-09 21:05:47 +00:00
|
|
|
Timer.periodic(Duration(milliseconds: 350), (timer) {
|
|
|
|
_timer = timer;
|
2021-04-05 15:35:51 +00:00
|
|
|
_currentIndex = lyrics?.lyrics?.lastIndexWhere(
|
|
|
|
(l) => l.offset <= AudioService.playbackState.currentPosition);
|
2020-11-09 21:05:47 +00:00
|
|
|
if (_loading) return;
|
|
|
|
//Scroll to current lyric
|
|
|
|
if (_currentIndex <= 0) return;
|
|
|
|
if (_prevIndex == _currentIndex) return;
|
2021-04-04 22:59:57 +00:00
|
|
|
//Update current lyric index
|
|
|
|
setState(() => null);
|
2020-11-09 21:05:47 +00:00
|
|
|
_prevIndex = _currentIndex;
|
2021-04-05 15:35:51 +00:00
|
|
|
if (_freeScroll) return;
|
|
|
|
_scrollToLyric();
|
2020-11-09 21:05:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
//Track change = exit lyrics
|
2021-02-09 20:14:14 +00:00
|
|
|
_mediaItemSub = AudioService.currentMediaItemStream.listen((event) {
|
2021-04-05 15:35:51 +00:00
|
|
|
if (event.id != widget.trackId) Navigator.of(context).pop();
|
2020-11-09 21:05:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-04-05 15:35:51 +00:00
|
|
|
if (_timer != null) _timer.cancel();
|
|
|
|
if (_mediaItemSub != null) _mediaItemSub.cancel();
|
2021-02-09 20:14:14 +00:00
|
|
|
//Stop visualizer
|
2021-04-05 15:35:51 +00:00
|
|
|
if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
|
2020-11-09 21:05:47 +00:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
2021-04-05 15:35:51 +00:00
|
|
|
appBar: FreezerAppBar('Lyrics'.i18n,
|
|
|
|
height: _freeScroll ? 100 : 56,
|
|
|
|
bottom: _freeScroll
|
|
|
|
? PreferredSize(
|
|
|
|
preferredSize: Size.fromHeight(46),
|
|
|
|
child: Theme(
|
|
|
|
data: settings.themeData.copyWith(
|
|
|
|
textButtonTheme: TextButtonThemeData(
|
|
|
|
style: ButtonStyle(
|
|
|
|
foregroundColor: MaterialStateProperty.all(
|
|
|
|
Colors.white)))),
|
|
|
|
child: Container(
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
TextButton(
|
|
|
|
onPressed: () {
|
|
|
|
setState(() => _freeScroll = false);
|
|
|
|
_scrollToLyric();
|
|
|
|
},
|
|
|
|
child: Text(
|
|
|
|
_currentIndex >= 0
|
|
|
|
? lyrics.lyrics[_currentIndex].text
|
|
|
|
: '...',
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
),
|
|
|
|
style: ButtonStyle(
|
|
|
|
foregroundColor:
|
|
|
|
MaterialStateProperty.all(Colors.white)))
|
|
|
|
],
|
|
|
|
)),
|
|
|
|
))
|
|
|
|
: null),
|
|
|
|
body: Stack(
|
|
|
|
children: [
|
|
|
|
//Visualizer
|
|
|
|
if (settings.lyricsVisualizer)
|
|
|
|
Align(
|
|
|
|
alignment: Alignment.bottomCenter,
|
|
|
|
child: StreamBuilder(
|
|
|
|
stream: playerHelper.visualizerStream,
|
|
|
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
|
|
|
List<double> data = snapshot.data ?? [];
|
|
|
|
double width =
|
|
|
|
MediaQuery.of(context).size.width / data.length -
|
|
|
|
0.25;
|
|
|
|
return Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: List.generate(
|
|
|
|
data.length,
|
|
|
|
(i) => AnimatedContainer(
|
|
|
|
duration: Duration(milliseconds: 130),
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
height: data[i] * 100,
|
|
|
|
width: width,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}),
|
2020-11-09 21:05:47 +00:00
|
|
|
),
|
2021-04-05 15:35:51 +00:00
|
|
|
|
|
|
|
//Lyrics
|
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(
|
|
|
|
0, 0, 0, settings.lyricsVisualizer ? 100 : 0),
|
|
|
|
child: _error
|
|
|
|
?
|
|
|
|
//Shouldn't really happen, empty lyrics have own text
|
|
|
|
ErrorScreen()
|
|
|
|
:
|
|
|
|
// Loading
|
|
|
|
_loading
|
|
|
|
? Padding(
|
|
|
|
padding: EdgeInsets.all(8.0),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [CircularProgressIndicator()],
|
|
|
|
),
|
2021-02-09 20:14:14 +00:00
|
|
|
)
|
2021-04-05 15:35:51 +00:00
|
|
|
: NotificationListener(
|
|
|
|
onNotification: (Notification notification) {
|
|
|
|
if (notification is! ScrollEndNotification)
|
|
|
|
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)
|
|
|
|
setState(() => _freeScroll = true);
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
child: ListView.builder(
|
|
|
|
controller: _controller,
|
|
|
|
itemCount: lyrics.lyrics.length,
|
|
|
|
itemBuilder: (BuildContext context, int i) {
|
|
|
|
return Padding(
|
|
|
|
padding:
|
|
|
|
EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
|
child: Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
borderRadius:
|
|
|
|
BorderRadius.circular(8.0),
|
|
|
|
color: _currentIndex == i
|
|
|
|
? Colors.grey.withOpacity(0.25)
|
|
|
|
: Colors.transparent,
|
|
|
|
),
|
|
|
|
height: height,
|
|
|
|
child: InkWell(
|
|
|
|
borderRadius:
|
|
|
|
BorderRadius.circular(8.0),
|
|
|
|
onTap: lyrics.id != null
|
|
|
|
? () => AudioService.seekTo(
|
|
|
|
lyrics.lyrics[i].offset)
|
|
|
|
: null,
|
|
|
|
child: Center(
|
|
|
|
child: Text(
|
|
|
|
lyrics.lyrics[i].text,
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 26.0,
|
|
|
|
fontWeight:
|
|
|
|
(_currentIndex == i)
|
|
|
|
? FontWeight.bold
|
|
|
|
: FontWeight.normal),
|
|
|
|
),
|
|
|
|
))));
|
|
|
|
},
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
));
|
2020-11-09 21:05:47 +00:00
|
|
|
}
|
|
|
|
}
|