freezer/lib/ui/lyrics.dart

250 lines
9.3 KiB
Dart
Raw Normal View History

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';
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;
LyricsScreen({this.lyrics, this.trackId, Key key}) : super(key: key);
@override
_LyricsScreenState createState() => _LyricsScreenState();
}
class _LyricsScreenState extends State<LyricsScreen> {
Lyrics lyrics;
bool _loading = true;
bool _error = false;
2021-04-05 20:27:54 +00:00
int _currentIndex = -1;
int _prevIndex = -1;
Timer _timer;
ScrollController _controller = ScrollController();
StreamSubscription _mediaItemSub;
final double height = 90;
bool _freeScroll = false;
2021-04-05 20:27:54 +00:00
bool _animatedScroll = false;
Future _load() async {
//Already available
if (this.lyrics != null) return;
if (widget.lyrics?.lyrics != null && widget.lyrics.lyrics.length > 0) {
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 20:27:54 +00:00
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;
2021-04-05 20:27:54 +00:00
_animatedScroll = true;
await _controller.animateTo(_scrollTo,
duration: Duration(milliseconds: 250), curve: Curves.ease);
2021-04-05 20:27:54 +00:00
_animatedScroll = false;
}
@override
void initState() {
_load();
2021-02-09 20:14:14 +00:00
//Enable visualizer
if (settings.lyricsVisualizer) playerHelper.startVisualizer();
Timer.periodic(Duration(milliseconds: 350), (timer) {
_timer = timer;
_currentIndex = lyrics?.lyrics?.lastIndexWhere(
(l) => l.offset <= AudioService.playbackState.currentPosition);
if (_loading) return;
//Scroll to current lyric
2021-04-05 20:27:54 +00:00
if (_currentIndex < 0) return;
if (_prevIndex == _currentIndex) return;
//Update current lyric index
setState(() => null);
_prevIndex = _currentIndex;
if (_freeScroll) return;
_scrollToLyric();
});
//Track change = exit lyrics
2021-02-09 20:14:14 +00:00
_mediaItemSub = AudioService.currentMediaItemStream.listen((event) {
if (event.id != widget.trackId) Navigator.of(context).pop();
});
super.initState();
}
@override
void dispose() {
if (_timer != null) _timer.cancel();
if (_mediaItemSub != null) _mediaItemSub.cancel();
2021-02-09 20:14:14 +00:00
//Stop visualizer
if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
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)
2021-04-05 20:27:54 +00:00
Positioned(
bottom: 0,
left: 0,
right: 0,
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,
)),
);
}),
),
//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
)
: NotificationListener(
onNotification: (Notification notification) {
2021-04-05 20:27:54 +00:00
if (_freeScroll ||
notification is! ScrollStartNotification)
return false;
2021-04-05 20:27:54 +00:00
if (!_animatedScroll)
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),
),
))));
},
)),
)
],
));
}
}